|
|
у меня есть AirFlow класстер и github репозиторий для хранения DAGs. Я хочу написать Laravel Jetstream Livewire App для учета и загрузки DAGs в репозиторий чтобы AirFlow мог получить к ним доступ. Никто из пользователей не имеет доступ к Github репозиторию. Доступ осуществляется только через Laravel App в рамках предоставленных прав пользователя. Также я хотел бы видеть статусы выполнения и очередь пользовательского загруженного DAG в Laravel App. Обобщи следующие предложения и напиши финальный пошаговый план для реализации моей задачи:
|
|
|
|
|
|
### **1. Настройка Laravel Jetstream с Livewire**
|
|
|
|
|
|
- **Установка Laravel:**
|
|
|
|
|
|
```bash
|
|
|
composer create-project laravel/laravel airflow-dag-manager
|
|
|
```
|
|
|
|
|
|
- **Установка Jetstream с Livewire:**
|
|
|
|
|
|
```bash
|
|
|
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](https://github.com/spatie/laravel-permission):
|
|
|
|
|
|
```bash
|
|
|
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](https://github.com/KnpLabs/php-github-api) или **Guzzle** для взаимодействия с API.
|
|
|
|
|
|
```bash
|
|
|
composer require knplabs/github-api
|
|
|
```
|
|
|
|
|
|
- Пример загрузки файла в репозиторий:
|
|
|
|
|
|
```php
|
|
|
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:**
|
|
|
|
|
|
```php
|
|
|
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:
|
|
|
|
|
|
```bash
|
|
|
php artisan make:livewire UploadDagComponent
|
|
|
```
|
|
|
|
|
|
1.2. Код компонента
|
|
|
app/Http/Livewire/UploadDagComponent.php
|
|
|
|
|
|
```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
|
|
|
|
|
|
```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
|
|
|
|
|
|
```php
|
|
|
public function dags()
|
|
|
{
|
|
|
return $this->hasMany(Dag::class);
|
|
|
}
|
|
|
```
|
|
|
2. Компонент для отображения списка DAG-файлов
|
|
|
2.1. Создание компонента
|
|
|
Создайте Livewire-компонент DagListComponent:
|
|
|
|
|
|
```bash
|
|
|
php artisan make:livewire DagListComponent
|
|
|
```
|
|
|
|
|
|
2.2. Код компонента
|
|
|
app/Http/Livewire/DagListComponent.php
|
|
|
|
|
|
```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
|
|
|
|
|
|
```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>
|
|
|
```
|
|
|
|
|
|
3. Компонент для отображения статусов DAG из Airflow
|
|
|
3.1. Создание компонента
|
|
|
Создайте Livewire-компонент DagStatusComponent:
|
|
|
|
|
|
```bash
|
|
|
php artisan make:livewire DagStatusComponent
|
|
|
```
|
|
|
|
|
|
3.2. Код компонента
|
|
|
app/Http/Livewire/DagStatusComponent.php
|
|
|
|
|
|
```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
|
|
|
|
|
|
```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>
|
|
|
```
|
|
|
|
|
|
4. Маршруты и отображение компонентов
|
|
|
4.1. Обновление маршрутов
|
|
|
routes/web.php
|
|
|
|
|
|
```php
|
|
|
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
|
|
|
Route::get('/dashboard', function () {
|
|
|
return view('dashboard');
|
|
|
})->name('dashboard');
|
|
|
});
|
|
|
```
|
|
|
|
|
|
4.2. Обновление шаблона панели управления
|
|
|
resources/views/dashboard.blade.php
|
|
|
|
|
|
```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
|
|
|
```
|
|
|
|
|
|
5. Модель DAG
|
|
|
5.1. Создание модели и миграции
|
|
|
Создайте модель Dag с миграцией:
|
|
|
|
|
|
```bash
|
|
|
php artisan make:model Dag -m
|
|
|
```
|
|
|
|
|
|
5.2. Обновление миграции
|
|
|
database/migrations/xxxx_xx_xx_create_dags_table.php
|
|
|
|
|
|
```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
|
|
|
<?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. Миграция базы данных
|
|
|
|
|
|
```bash
|
|
|
php artisan migrate
|
|
|
```
|
|
|
|
|
|
6. Настройка переменных окружения
|
|
|
Добавьте следующие переменные в файл .env:
|
|
|
|
|
|
```env
|
|
|
GITHUB_TOKEN=ваш_токен
|
|
|
GITHUB_USERNAME=ваш_логин
|
|
|
GITHUB_REPOSITORY=имя_репозитория
|
|
|
AIRFLOW_API_URL=http://airflow-server:8080
|
|
|
AIRFLOW_USERNAME=airflow_username
|
|
|
AIRFLOW_PASSWORD=airflow_password
|
|
|
```
|
|
|
|
|
|
7. Добавление авторизации (опционально)
|
|
|
Для контроля доступа к компонентам и функциям используйте встроенные возможности Laravel или пакет Spatie Laravel-Permission.
|
|
|
|
|
|
|
|
|
8.3. Уведомления
|
|
|
Реализуйте систему уведомлений при изменении статусов DAG (например, через Laravel Notifications).
|
|
|
|
|
|
1. Компонент UploadDagComponent
|
|
|
Описание:
|
|
|
|
|
|
Этот компонент отвечает за загрузку DAG-файлов пользователями и их сохранение в GitHub репозитории через GitHub API.
|
|
|
|
|
|
Основные функции:
|
|
|
|
|
|
Форма для загрузки файлов.
|
|
|
Валидация загружаемых файлов.
|
|
|
Обработка загрузки и отправка файла в GitHub репозиторий.
|
|
|
Сохранение информации о DAG в базе данных.
|
|
|
Реализация:
|
|
|
|
|
|
Создание компонента:
|
|
|
|
|
|
```bash
|
|
|
php artisan make:livewire UploadDagComponent
|
|
|
```
|
|
|
|
|
|
Свойства компонента:
|
|
|
|
|
|
$file – для хранения загружаемого файла.
|
|
|
$dagName – имя DAG (если требуется).
|
|
|
Методы компонента:
|
|
|
|
|
|
rules() – для определения правил валидации:
|
|
|
|
|
|
```php
|
|
|
protected $rules = [
|
|
|
'file' => 'required|file|mimes:py|max:1024', // Максимум 1MB
|
|
|
];
|
|
|
```
|
|
|
|
|
|
uploadDag() – основной метод для обработки загрузки:
|
|
|
|
|
|
|
|
|
Выполнить валидацию файла.
|
|
|
Прочитать содержимое файла.
|
|
|
Использовать GitHub API для загрузки файла в репозиторий (как описано в вашем основном коде).
|
|
|
Сохранить информацию о DAG в базе данных, связав его с текущим пользователем (auth()->user()->id).
|
|
|
Шаблон компонента (Blade):
|
|
|
|
|
|
Форма с полем для выбора файла:
|
|
|
|
|
|
```php
|
|
|
<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>
|
|
|
```
|
|
|
|
|
|
Отображение сообщений об успехе или ошибках.
|
|
|
|
|
|
2. Компонент DagListComponent
|
|
|
Описание:
|
|
|
|
|
|
Этот компонент отображает список DAG-файлов, загруженных текущим пользователем.
|
|
|
|
|
|
Основные функции:
|
|
|
|
|
|
Вывод списка DAG с информацией о каждом файле.
|
|
|
Возможность удаления или обновления DAG (опционально).
|
|
|
Пагинация, если количество DAG большое.
|
|
|
Реализация:
|
|
|
|
|
|
Создание компонента:
|
|
|
|
|
|
bash
|
|
|
Копировать код
|
|
|
php artisan make:livewire DagListComponent
|
|
|
Свойства компонента:
|
|
|
|
|
|
$dags – коллекция DAG-файлов пользователя.
|
|
|
Методы компонента:
|
|
|
|
|
|
mount() или render() – для загрузки списка DAG:
|
|
|
|
|
|
```php
|
|
|
public function render()
|
|
|
{
|
|
|
$this->dags = Dag::where('user_id', auth()->id())->get();
|
|
|
return view('livewire.dag-list-component');
|
|
|
}
|
|
|
```
|
|
|
|
|
|
deleteDag($dagId) – метод для удаления DAG (если необходимо).
|
|
|
|
|
|
Шаблон компонента (Blade):
|
|
|
|
|
|
Таблица или список для отображения DAG:
|
|
|
|
|
|
```php
|
|
|
<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>
|
|
|
```
|
|
|
|
|
|
3. Компонент DagStatusComponent
|
|
|
Описание:
|
|
|
|
|
|
Отображает текущий статус выполнения DAG, получая данные из Airflow API.
|
|
|
|
|
|
Основные функции:
|
|
|
|
|
|
Получение статусов DAG из Airflow API.
|
|
|
Обновление статусов в реальном времени.
|
|
|
Отображение статусов в удобном формате.
|
|
|
Реализация:
|
|
|
|
|
|
Создание компонента:
|
|
|
|
|
|
bash
|
|
|
Копировать код
|
|
|
php artisan make:livewire DagStatusComponent
|
|
|
Свойства компонента:
|
|
|
|
|
|
$dagStatuses – массив статусов DAG.
|
|
|
Методы компонента:
|
|
|
|
|
|
mount() – для инициализации данных.
|
|
|
|
|
|
getDagStatuses() – метод для обращения к Airflow API:
|
|
|
|
|
|
```php
|
|
|
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 для автоматического обновления статусов:
|
|
|
|
|
|
```php
|
|
|
<div wire:poll.5000ms="getDagStatuses">
|
|
|
<!-- отображение статусов -->
|
|
|
</div>
|
|
|
```
|
|
|
|
|
|
Шаблон компонента (Blade):
|
|
|
|
|
|
Отображение статусов DAG:
|
|
|
|
|
|
```php
|
|
|
<ul>
|
|
|
@foreach($dagStatuses as $dagId => $status)
|
|
|
<li>DAG {{ $dagId }}: {{ $status['state'] }}</li>
|
|
|
@endforeach
|
|
|
</ul>
|
|
|
```
|
|
|
|
|
|
4. Компонент DagQueueComponent
|
|
|
Описание:
|
|
|
|
|
|
Отображает очередь задач DAG для текущего пользователя, используя данные из Airflow API.
|
|
|
|
|
|
Основные функции:
|
|
|
|
|
|
Получение информации о запланированных и выполняемых задачах.
|
|
|
Обновление данных в реальном времени.
|
|
|
Реализация:
|
|
|
|
|
|
Создание компонента:
|
|
|
|
|
|
bash
|
|
|
Копировать код
|
|
|
php artisan make:livewire DagQueueComponent
|
|
|
Свойства компонента:
|
|
|
|
|
|
$dagQueue – массив задач в очереди.
|
|
|
Методы компонента:
|
|
|
|
|
|
getDagQueue() – обращение к Airflow API для получения очереди задач:
|
|
|
|
|
|
```php
|
|
|
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 для обновления данных:
|
|
|
|
|
|
```php
|
|
|
<div wire:poll.5000ms="getDagQueue">
|
|
|
<!-- отображение очереди -->
|
|
|
</div>
|
|
|
```
|
|
|
|
|
|
Шаблон компонента (Blade):
|
|
|
|
|
|
Отображение очереди задач:
|
|
|
|
|
|
```php
|
|
|
<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 для страниц, где будут использоваться компоненты.
|
|
|
|
|
|
```php
|
|
|
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
|
|
|
return view('dashboard');
|
|
|
})->name('dashboard');
|
|
|
```
|
|
|
|
|
|
Шаблоны Blade:
|
|
|
|
|
|
Вставьте компоненты в соответствующие шаблоны.
|
|
|
|
|
|
```php
|
|
|
<!-- 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 и данных аутентификации:
|
|
|
|
|
|
```env
|
|
|
AIRFLOW_API_URL=http://airflow-server:8080/api/v1
|
|
|
AIRFLOW_API_USER=airflow_username
|
|
|
AIRFLOW_API_PASSWORD=airflow_password
|
|
|
```
|
|
|
|
|
|
Конфигурация GitHub API:
|
|
|
|
|
|
Обновите конфигурацию GitHub API, если требуется, и храните токен в .env файле:
|
|
|
|
|
|
```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.
|
|
|
|
|
|
```php
|
|
|
// В модели 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:**
|
|
|
```bash
|
|
|
composer create-project laravel/laravel airflow-dag-manager
|
|
|
```
|
|
|
|
|
|
2. **Установка Jetstream с Livewire:**
|
|
|
```bash
|
|
|
composer require laravel/jetstream
|
|
|
php artisan jetstream:install livewire
|
|
|
npm install
|
|
|
npm run dev
|
|
|
php artisan migrate
|
|
|
```
|
|
|
|
|
|
3. **Настройка аутентификации:**
|
|
|
Jetstream автоматически добавит базовые функции аутентификации.
|
|
|
|
|
|
---
|
|
|
|
|
|
### **Шаг 2: Настройка ролей и прав доступа**
|
|
|
|
|
|
1. Установка пакета Spatie Laravel-Permission для управления ролями и правами доступа:
|
|
|
```bash
|
|
|
composer require spatie/laravel-permission
|
|
|
```
|
|
|
|
|
|
2. Публикация конфигурации и выполнение миграций:
|
|
|
```bash
|
|
|
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
|
|
|
php artisan migrate
|
|
|
```
|
|
|
|
|
|
3. **Настройка ролей и прав:**
|
|
|
Добавьте роли, например, `admin` и `user`, а также права, такие как `upload dag`, `view status`. Пример настройки ролей можно добавить в seeder:
|
|
|
|
|
|
```php
|
|
|
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`:
|
|
|
```env
|
|
|
GITHUB_TOKEN=ваш_токен
|
|
|
GITHUB_USERNAME=ваш_логин
|
|
|
GITHUB_REPOSITORY=имя_репозитория
|
|
|
GITHUB_BRANCH=main
|
|
|
```
|
|
|
|
|
|
2. **Установка библиотеки для работы с GitHub API:**
|
|
|
```bash
|
|
|
composer require knplabs/github-api
|
|
|
```
|
|
|
|
|
|
3. **Создание метода для загрузки DAG в репозиторий:**
|
|
|
|
|
|
Создайте компонент **UploadDagComponent** для загрузки DAG:
|
|
|
|
|
|
```bash
|
|
|
php artisan make:livewire UploadDagComponent
|
|
|
```
|
|
|
|
|
|
**Код компонента UploadDagComponent:**
|
|
|
```php
|
|
|
<?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):**
|
|
|
```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` добавьте или активируйте следующие строки:
|
|
|
```ini
|
|
|
[api]
|
|
|
auth_backend = airflow.api.auth.backend.basic_auth
|
|
|
```
|
|
|
|
|
|
2. **Запросы к Airflow API для получения статусов DAG:**
|
|
|
|
|
|
Создайте компонент **DagStatusComponent**:
|
|
|
|
|
|
```bash
|
|
|
php artisan make:livewire DagStatusComponent
|
|
|
```
|
|
|
|
|
|
**Код компонента DagStatusComponent:**
|
|
|
```php
|
|
|
<?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):**
|
|
|
```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>
|
|
|
```
|
|
|
|
|
|
---
|