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