parent
08ba19f355
commit
563721e67c
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\airflow;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class AirFlowTargetTable extends BaseTableComponent
|
||||||
|
{
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->headers = ['ID', 'Имя', 'URL', 'Username', 'Порт', 'Версия', 'Активный'];
|
||||||
|
$this->data = Airflow::all(['id', 'name', 'url', 'username', 'port', 'version', 'is_active'])->toArray();
|
||||||
|
$this->view_item_route = "airflow.view";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\airflow;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class AirflowDataView extends Component
|
||||||
|
{
|
||||||
|
public $airflowId;
|
||||||
|
public $airflow;
|
||||||
|
|
||||||
|
public function mount($airflowId)
|
||||||
|
{
|
||||||
|
$this->airflowId = $airflowId;
|
||||||
|
$this->loadAirflowData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadAirflowData()
|
||||||
|
{
|
||||||
|
$this->airflow = Airflow::with('user')->find($this->airflowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.airflow-data-view');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\airflow;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class AirflowForm extends Component
|
||||||
|
{
|
||||||
|
public $name;
|
||||||
|
public $url;
|
||||||
|
public $username;
|
||||||
|
public $password;
|
||||||
|
public $api_token;
|
||||||
|
public $port;
|
||||||
|
public $version;
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'url' => 'required|url',
|
||||||
|
'username' => 'required|string|max:255',
|
||||||
|
'password' => 'required|string|max:255',
|
||||||
|
'api_token' => 'nullable|string|max:255',
|
||||||
|
'port' => 'required|integer',
|
||||||
|
'version' => 'nullable|string|max:255',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Airflow::create([
|
||||||
|
'name' => $this->name,
|
||||||
|
'url' => $this->url,
|
||||||
|
'username' => $this->username,
|
||||||
|
'password' => $this->password,
|
||||||
|
'api_token' => $this->api_token,
|
||||||
|
'port' => $this->port,
|
||||||
|
'version' => $this->version,
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
session()->flash('message', 'Airflow cluster created successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.airflow-form');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class BaseTableComponent extends Component
|
||||||
|
{
|
||||||
|
public $data = [];
|
||||||
|
public $headers = [];
|
||||||
|
public $view_item_route = null;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.base-table-component');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class UsersTable extends Component
|
||||||
|
{
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.users-table');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'models' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* Eloquent model should be used to retrieve your permissions. Of course, it
|
||||||
|
* is often just the "Permission" model but you may use whatever you like.
|
||||||
|
*
|
||||||
|
* The model you want to use as a Permission model needs to implement the
|
||||||
|
* `Spatie\Permission\Contracts\Permission` contract.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'permission' => Spatie\Permission\Models\Permission::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* Eloquent model should be used to retrieve your roles. Of course, it
|
||||||
|
* is often just the "Role" model but you may use whatever you like.
|
||||||
|
*
|
||||||
|
* The model you want to use as a Role model needs to implement the
|
||||||
|
* `Spatie\Permission\Contracts\Role` contract.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'role' => Spatie\Permission\Models\Role::class,
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
'table_names' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your roles. We have chosen a basic
|
||||||
|
* default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'roles' => 'roles',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your permissions. We have chosen a basic
|
||||||
|
* default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'permissions' => 'permissions',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your models permissions. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_has_permissions' => 'model_has_permissions',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your models roles. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_has_roles' => 'model_has_roles',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your roles permissions. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'role_has_permissions' => 'role_has_permissions',
|
||||||
|
],
|
||||||
|
|
||||||
|
'column_names' => [
|
||||||
|
/*
|
||||||
|
* Change this if you want to name the related pivots other than defaults
|
||||||
|
*/
|
||||||
|
'role_pivot_key' => null, //default 'role_id',
|
||||||
|
'permission_pivot_key' => null, //default 'permission_id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change this if you want to name the related model primary key other than
|
||||||
|
* `model_id`.
|
||||||
|
*
|
||||||
|
* For example, this would be nice if your primary keys are all UUIDs. In
|
||||||
|
* that case, name this `model_uuid`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_morph_key' => 'model_id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change this if you want to use the teams feature and your related model's
|
||||||
|
* foreign key is other than `team_id`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'team_foreign_key' => 'team_id',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the method for checking permissions will be registered on the gate.
|
||||||
|
* Set this to false if you want to implement custom logic for checking permissions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'register_permission_check_method' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
|
||||||
|
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
|
||||||
|
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
|
||||||
|
*/
|
||||||
|
'register_octane_reset_listener' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Teams Feature.
|
||||||
|
* When set to true the package implements teams using the 'team_foreign_key'.
|
||||||
|
* If you want the migrations to register the 'team_foreign_key', you must
|
||||||
|
* set this to true before doing the migration.
|
||||||
|
* If you already did the migration then you must make a new migration to also
|
||||||
|
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
|
||||||
|
* (view the latest version of this package's migration file)
|
||||||
|
*/
|
||||||
|
|
||||||
|
'teams' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Passport Client Credentials Grant
|
||||||
|
* When set to true the package will use Passports Client to check permissions
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use_passport_client_credentials' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the required permission names are added to exception messages.
|
||||||
|
* This could be considered an information leak in some contexts, so the default
|
||||||
|
* setting is false here for optimum safety.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'display_permission_in_exception' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the required role names are added to exception messages.
|
||||||
|
* This could be considered an information leak in some contexts, so the default
|
||||||
|
* setting is false here for optimum safety.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'display_role_in_exception' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default wildcard permission lookups are disabled.
|
||||||
|
* See documentation to understand supported syntax.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enable_wildcard_permission' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class to use for interpreting wildcard permissions.
|
||||||
|
* If you need to modify delimiters, override the class and specify its name here.
|
||||||
|
*/
|
||||||
|
// 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||||
|
|
||||||
|
/* Cache-specific settings */
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default all permissions are cached for 24 hours to speed up performance.
|
||||||
|
* When permissions or roles are updated the cache is flushed automatically.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The cache key used to store all permissions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'key' => 'spatie.permission.cache',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You may optionally indicate a specific cache driver to use for permission and
|
||||||
|
* role caching using any of the `store` drivers listed in the cache.php config
|
||||||
|
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'store' => 'default',
|
||||||
|
],
|
||||||
|
];
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('airflows', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name'); // Имя кластера
|
||||||
|
$table->string('url'); // URL или IP кластера Airflow
|
||||||
|
$table->string('username')->nullable(); // Логин для доступа (если используется)
|
||||||
|
$table->string('password')->nullable(); // Пароль для доступа (если используется)
|
||||||
|
$table->string('api_token')->nullable(); // Токен для доступа (если используется)
|
||||||
|
$table->string('port')->default('8080'); // Порт Airflow API
|
||||||
|
$table->string('version')->nullable(); // Версия Airflow
|
||||||
|
$table->boolean('is_active')->default(true); // Статус кластера (активен/неактивен)
|
||||||
|
$table->foreignId('user_id')->constrained(); // Кто создал/управляет кластером
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('airflows');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('dags', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
$table->foreignId('airflow_id')->constrained('airflows')->onDelete('cascade');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
$table->string('file_path');
|
||||||
|
$table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
|
||||||
|
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('dags');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$teams = config('permission.teams');
|
||||||
|
$tableNames = config('permission.table_names');
|
||||||
|
$columnNames = config('permission.column_names');
|
||||||
|
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||||
|
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||||
|
|
||||||
|
if (empty($tableNames)) {
|
||||||
|
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||||
|
}
|
||||||
|
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
|
||||||
|
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::create($tableNames['permissions'], function (Blueprint $table) {
|
||||||
|
//$table->engine('InnoDB');
|
||||||
|
$table->bigIncrements('id'); // permission id
|
||||||
|
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||||
|
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['name', 'guard_name']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) {
|
||||||
|
//$table->engine('InnoDB');
|
||||||
|
$table->bigIncrements('id'); // role id
|
||||||
|
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||||
|
}
|
||||||
|
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||||
|
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||||
|
$table->timestamps();
|
||||||
|
if ($teams || config('permission.testing')) {
|
||||||
|
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||||
|
} else {
|
||||||
|
$table->unique(['name', 'guard_name']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||||
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||||
|
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||||
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
|
->references('id') // permission id
|
||||||
|
->on($tableNames['permissions'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
if ($teams) {
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||||
|
|
||||||
|
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_permissions_permission_model_type_primary');
|
||||||
|
} else {
|
||||||
|
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_permissions_permission_model_type_primary');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||||
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||||
|
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||||
|
|
||||||
|
$table->foreign($pivotRole)
|
||||||
|
->references('id') // role id
|
||||||
|
->on($tableNames['roles'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
if ($teams) {
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||||
|
|
||||||
|
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary');
|
||||||
|
} else {
|
||||||
|
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||||
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
|
->references('id') // permission id
|
||||||
|
->on($tableNames['permissions'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
|
||||||
|
$table->foreign($pivotRole)
|
||||||
|
->references('id') // role id
|
||||||
|
->on($tableNames['roles'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
|
||||||
|
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||||
|
});
|
||||||
|
|
||||||
|
app('cache')
|
||||||
|
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||||
|
->forget(config('permission.cache.key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$tableNames = config('permission.table_names');
|
||||||
|
|
||||||
|
if (empty($tableNames)) {
|
||||||
|
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::drop($tableNames['role_has_permissions']);
|
||||||
|
Schema::drop($tableNames['model_has_roles']);
|
||||||
|
Schema::drop($tableNames['model_has_permissions']);
|
||||||
|
Schema::drop($tableNames['roles']);
|
||||||
|
Schema::drop($tableNames['permissions']);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('dag_events', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('dag_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->foreignId('dag_run_id')->nullable()->constrained()->onDelete('cascade');
|
||||||
|
$table->string('event_type'); // Тип события: 'failure', 'success', 'task_failed', и т.д.
|
||||||
|
$table->text('message')->nullable(); // Дополнительное сообщение
|
||||||
|
$table->json('metadata')->nullable(); // Дополнительные данные
|
||||||
|
$table->timestamps(); // created_at будет временем события
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('dag_events');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('task_instances', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('dag_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->foreignId('dag_run_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->foreignId('airflow_id')->after('dag_id')->constrained('airflows')->onDelete('cascade');
|
||||||
|
$table->string('task_id'); // ID задачи в Airflow
|
||||||
|
$table->enum('state', [
|
||||||
|
'success',
|
||||||
|
'running',
|
||||||
|
'failed',
|
||||||
|
'queued',
|
||||||
|
'skipped',
|
||||||
|
'up_for_retry',
|
||||||
|
'upstream_failed',
|
||||||
|
'up_for_reschedule',
|
||||||
|
'none',
|
||||||
|
])->nullable();
|
||||||
|
$table->timestamp('execution_date')->nullable();
|
||||||
|
$table->timestamp('start_date')->nullable();
|
||||||
|
$table->timestamp('end_date')->nullable();
|
||||||
|
$table->longText('log')->nullable(); // Логи выполнения задачи
|
||||||
|
$table->json('metadata')->nullable(); // Дополнительные данные
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('task_instances');
|
||||||
|
}
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,39 @@
|
|||||||
|
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-xl font-bold text-gray-900">
|
||||||
|
Airflow Cluster: {{ $airflow->name }}
|
||||||
|
</h2>
|
||||||
|
<span class="text-sm text-gray-500">
|
||||||
|
Updated at: {{ now()->format('H:i:s') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<p><strong>URL:</strong> <a href="{{ $airflow->url }}" class="text-blue-500" target="_blank">{{ $airflow->url }}</a></p>
|
||||||
|
<p><strong>Version:</strong> {{ $airflow->version }}</p>
|
||||||
|
<p><strong>Port:</strong> {{ $airflow->port }}</p>
|
||||||
|
<p><strong>Status:</strong>
|
||||||
|
<span class="px-2 py-1 rounded-full text-sm {{ $airflow->is_active ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
|
||||||
|
{{ $airflow->is_active ? 'Active' : 'Inactive' }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p><strong>Managed by:</strong> {{ $airflow->user->name }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-800">DAGs in this cluster:</h3>
|
||||||
|
<ul class="mt-2 list-disc list-inside">
|
||||||
|
@foreach ($airflow->dags as $dag)
|
||||||
|
<li>{{ $dag->name }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('livewire:load', function () {
|
||||||
|
setInterval(() => {
|
||||||
|
Livewire.emit('refreshData');
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,53 @@
|
|||||||
|
<div class="max-w-lg mx-auto p-6 bg-white rounded-lg shadow-lg">
|
||||||
|
@if (session()->has('message'))
|
||||||
|
<div class="mb-4 p-4 bg-green-100 text-green-700 rounded-lg">
|
||||||
|
{{ session('message') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form wire:submit.prevent="submit" class="space-y-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name" class="block text-gray-700 font-semibold">Name</label>
|
||||||
|
<input type="text" id="name" wire:model="name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
|
@error('name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="url" class="block text-gray-700 font-semibold">URL</label>
|
||||||
|
<input type="text" id="url" wire:model="url" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
|
@error('url') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username" class="block text-gray-700 font-semibold">Username</label>
|
||||||
|
<input type="text" id="username" wire:model="username" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
|
@error('username') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password" class="block text-gray-700 font-semibold">Password</label>
|
||||||
|
<input type="password" id="password" wire:model="password" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
|
@error('password') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="api_token" class="block text-gray-700 font-semibold">API Token</label>
|
||||||
|
<input type="text" id="api_token" wire:model="api_token" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
|
@error('api_token') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="port" class="block text-gray-700 font-semibold">Port</label>
|
||||||
|
<input type="text" id="port" wire:model="port" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
|
@error('port') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="version" class="block text-gray-700 font-semibold">Version</label>
|
||||||
|
<input type="text" id="version" wire:model="version" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
|
@error('version') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="w-full bg-blue-500 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
@ -0,0 +1,33 @@
|
|||||||
|
<div class="bg-white shadow-lg rounded-xl p-6 overflow-hidden">
|
||||||
|
@if(empty($data) || count($data) == 0)
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<svg class="w-16 h-16 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2a4 4 0 00-4-4H3m18 0h-2a4 4 0 00-4 4v2m-4-4v6m4-6v6M7 17v6m10-6v6"/>
|
||||||
|
</svg>
|
||||||
|
<p class="text-gray-600 mt-4">Нет данных для отображения.</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<table class="min-w-full bg-gray-50 rounded-lg shadow-md">
|
||||||
|
<thead class="bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700">
|
||||||
|
<tr>
|
||||||
|
@foreach($headers as $header)
|
||||||
|
<th class="px-6 py-4 text-left text-sm font-semibold text-gray-800 border-b border-gray-300">
|
||||||
|
{{ $header }}
|
||||||
|
</th>
|
||||||
|
@endforeach
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-gray-700">
|
||||||
|
@foreach($data as $row)
|
||||||
|
<tr class="bg-white hover:bg-gray-50 transition-colors shadow-sm hover:shadow-lg">
|
||||||
|
@foreach($row as $cell)
|
||||||
|
<td class="px-6 py-4 border-b border-gray-200 text-sm">
|
||||||
|
{{ $cell }}
|
||||||
|
</td>
|
||||||
|
@endforeach
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
@endif
|
||||||
|
</div>
|
@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
{{-- Stop trying to control. --}}
|
||||||
|
</div>
|
@ -0,0 +1,19 @@
|
|||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||||
|
AirFlow Clusters
|
||||||
|
</h2>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div class="py-12">
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 flex space-x-4">
|
||||||
|
<div class="w-1/3">
|
||||||
|
<livewire:airflow-data-view :airflowId="$id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-2/3">
|
||||||
|
HERE WAS A TABLE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-app-layout>
|
@ -0,0 +1,19 @@
|
|||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||||
|
AirFlow Clusters
|
||||||
|
</h2>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div class="py-12">
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 flex space-x-4">
|
||||||
|
<div class="w-1/3">
|
||||||
|
<livewire:airflow-form />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-2/3">
|
||||||
|
<livewire:air-flow-target-table />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-app-layout>
|
@ -0,0 +1,19 @@
|
|||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||||
|
All Dags
|
||||||
|
</h2>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div class="py-12">
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 flex space-x-4">
|
||||||
|
<div class="w-1/3">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-2/3">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-app-layout>
|
@ -0,0 +1 @@
|
|||||||
|
<?php
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue