diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..ed3b5ef
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,13 @@
+{
+ "plugins": ["prettier-plugin-blade"],
+ "overrides": [
+ {
+ "files": ["*.blade.php"],
+ "options": {
+ "parser": "blade"
+ }
+ }
+ ],
+ "singleQuote": true,
+ "tabWidth": 4
+}
diff --git a/app/Bots/BotContract.php b/app/Bots/BotContract.php
new file mode 100644
index 0000000..8379329
--- /dev/null
+++ b/app/Bots/BotContract.php
@@ -0,0 +1,9 @@
+client = new Client([
+ 'base_uri' => rtrim(config('scheduler.mattermost.server_url'), '/') . '/api/v4/',
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . config('scheduler.mattermost.access_token'),
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ ],
+ ]);
+ }
+
+ public function run(): void
+ {
+ try {
+ $request = new \GuzzleHttp\Psr7\Request(
+ $this->config['method'] ?? 'POST',
+ $this->config['endpoint'],
+ ['Content-Type' => 'application/json'],
+ json_encode($this->config['body'] ?? [])
+ );
+
+ $res = $this->client->send($request);
+
+ // $res = $this->client->put('users/me/status/custom', [
+ // 'json' => [
+ // 'emoji' => 'house',
+ // 'text' => 'Working Home',
+ // 'status' => 'online',
+ // ]
+ // ]);
+
+ if ($res->getStatusCode() === 200) {
+ Log::info("Mattermost home status updated successfully.");
+ } else {
+ Log::error("Failed to update Mattermost status. HTTP Status: " . $res->getStatusCode());
+ }
+ } catch (\Exception $e) {
+ Log::error("Error updating Mattermost status: " . $e->getMessage());
+ }
+ }
+
+ public static function configSchema(): array
+ {
+ return [
+ 'endpoint' => ['type' => 'string', 'label' => 'API Endpoint', 'rules' => [
+ 'required',
+ 'string',
+ 'max:255',
+ ]],
+ 'method' => ['type' => 'string', 'label' => 'HTTP Method', 'default' => 'POST', 'rules' => [
+ 'required',
+ 'in:GET,POST,PUT,DELETE,PATCH',
+ ]],
+ 'body' => ['type' => 'array', 'label' => 'Request Body (JSON)', 'rules' => [
+ 'nullable',
+ 'array',
+ ]],
+ ];
+ }
+}
diff --git a/app/Bots/Mattermost/RemoveStatus.php b/app/Bots/Mattermost/RemoveStatus.php
new file mode 100644
index 0000000..2103e37
--- /dev/null
+++ b/app/Bots/Mattermost/RemoveStatus.php
@@ -0,0 +1,46 @@
+client = new Client([
+ 'base_uri' => rtrim(config('scheduler.mattermost.server_url'), '/') . '/api/v4/',
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . config('scheduler.mattermost.access_token'),
+ ],
+ ]);
+ }
+
+ public function run(): void
+ {
+ try {
+ $res = $this->client->delete('users/me/status/custom');
+
+ if ($res->getStatusCode() === 200) {
+ Log::info("Mattermost status cleared");
+ } else {
+ Log::error("Failed to update Mattermost status. HTTP Status: " . $res->getStatusCode());
+ }
+ } catch (\Exception $e) {
+ Log::error("Error updating Mattermost status: " . $e->getMessage());
+ }
+ }
+
+ public static function configSchema(): array
+ {
+ return [
+ 'endpoint' => ['type' => 'string', 'label' => 'API Endpoint'],
+ 'method' => ['type' => 'string', 'label' => 'HTTP Method', 'default' => 'POST'],
+ 'body' => ['type' => 'array', 'label' => 'Request Body (JSON)'],
+ ];
+ }
+}
diff --git a/app/Bots/Mattermost/WednesdayHomeStatus.php b/app/Bots/Mattermost/WednesdayHomeStatus.php
new file mode 100644
index 0000000..168ad91
--- /dev/null
+++ b/app/Bots/Mattermost/WednesdayHomeStatus.php
@@ -0,0 +1,54 @@
+client = new Client([
+ 'base_uri' => rtrim(config('scheduler.mattermost.server_url'), '/') . '/api/v4/',
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . config('scheduler.mattermost.access_token'),
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ ],
+ ]);
+ }
+
+ public function run(): void
+ {
+ try {
+ $res = $this->client->put('users/me/status/custom', [
+ 'json' => [
+ 'emoji' => 'house',
+ 'text' => 'Working Home',
+ 'status' => 'online',
+ ]
+ ]);
+
+ if ($res->getStatusCode() === 200) {
+ Log::info("Mattermost home status updated successfully.");
+ } else {
+ Log::error("Failed to update Mattermost status. HTTP Status: " . $res->getStatusCode());
+ }
+ } catch (\Exception $e) {
+ Log::error("Error updating Mattermost status: " . $e->getMessage());
+ }
+ }
+
+ public static function configSchema(): array
+ {
+ return [
+ 'endpoint' => ['type' => 'string', 'label' => 'API Endpoint'],
+ 'method' => ['type' => 'string', 'label' => 'HTTP Method', 'default' => 'POST'],
+ 'body' => ['type' => 'array', 'label' => 'Request Body (JSON)'],
+ ];
+ }
+}
diff --git a/app/Http/Controllers/BotController.php b/app/Http/Controllers/BotController.php
new file mode 100644
index 0000000..3527bd5
--- /dev/null
+++ b/app/Http/Controllers/BotController.php
@@ -0,0 +1,13 @@
+bots = \App\Models\Bot::all();
+ }
+
+ public function toggleBot($botId)
+ {
+ $bot = \App\Models\Bot::find($botId);
+
+ if ($bot) {
+ $bot->enabled = !$bot->enabled;
+ $bot->save();
+ $this->bots = \App\Models\Bot::all(); // Refresh the list
+ flash()->success("Bot '{$bot->name}' has been " . ($bot->enabled ? 'enabled' : 'disabled') . ".");
+ }
+ }
+
+ public function render()
+ {
+ return view('livewire.bots-list');
+ }
+}
diff --git a/app/Livewire/CreateEditBot.php b/app/Livewire/CreateEditBot.php
new file mode 100644
index 0000000..2321f00
--- /dev/null
+++ b/app/Livewire/CreateEditBot.php
@@ -0,0 +1,164 @@
+routeName = URL::current() === route('bots.create') ? 'Create Bot' : 'Edit Bot';
+
+ $bot = \App\Models\Bot::find($bot);
+ if ($bot) {
+ $this->name = $bot->name;
+ $this->class = $bot->class;
+ $this->schedule = $bot->schedule;
+ $this->enabled = $bot->enabled;
+ $this->cron_text = \Lorisleiva\CronTranslator\CronTranslator::translate($bot->schedule);
+ }
+
+ $basePath = $basePath ?? app_path('Bots');
+ $baseNamespace = $baseNamespace ?? 'App\\Bots';
+
+ foreach (File::allFiles($basePath) as $file) {
+ // Convert file path → class name
+ $relativePath = str_replace([$basePath . DIRECTORY_SEPARATOR, '.php'], '', $file->getPathname());
+ $relativeNamespace = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
+ $class = $baseNamespace . '\\' . $relativeNamespace;
+
+ // Make sure class exists and implements BotContract
+ if (class_exists($class) && in_array(BotContract::class, class_implements($class))) {
+ $label = method_exists($class, 'label')
+ ? $class::label()
+ : class_basename($class);
+
+ $this->classList[$label] = $class;
+ }
+ }
+ }
+
+ public function updatedClass($value)
+ {
+ $this->class = $this->classList[$value];
+ $this->configSchema = $this->classList[$value]::configSchema();
+ }
+
+ protected function rules(): array
+ {
+ $schemaRules = [];
+
+ foreach ($this->configSchema as $field => $meta) {
+ $schemaRules["config.$field"] = $meta['rules'] ?? [];
+ }
+
+ return array_merge([
+ 'name' => [
+ 'required',
+ 'string',
+ 'max:255',
+ Rule::unique('bots', 'name')->ignore($this->name, 'name'),
+ ],
+ 'class' => [
+ 'required',
+ 'string',
+ 'max:255',
+ ],
+ 'schedule' => [
+ 'required',
+ 'string',
+ 'max:255',
+ function ($attribute, $value, $fail) {
+ if (!CronExpression::isValidExpression($value)) {
+ $fail('The ' . $attribute . ' is not a valid cron expression.');
+ }
+ },
+ ],
+ 'enabled' => ['boolean'],
+ ], $schemaRules);
+ }
+
+ public function save(): void
+ {
+ $this->validate();
+
+ if (!class_exists($this->class)) {
+ $this->addError('class', 'The specified class does not exist.');
+ return;
+ }
+
+ if (!in_array(\App\Bots\BotContract::class, class_implements($this->class))) {
+ $this->addError('class', 'The specified class does not exist.');
+ return;
+ }
+
+ if (URL::current() !== route('bots.create')) {
+ $bot = \App\Models\Bot::where('name', $this->name)->first();
+ if ($bot) {
+ $bot->update([
+ 'name' => $this->name,
+ 'class' => $this->class,
+ 'enabled' => $this->enabled,
+ 'schedule' => $this->schedule,
+ ]);
+
+ flash()->success('Bot updated successfully.');
+
+ return;
+ }
+ } else {
+ \App\Models\Bot::create([
+ 'name' => $this->name,
+ 'class' => $this->class,
+ 'enabled' => $this->enabled,
+ 'schedule' => $this->schedule,
+ ]);
+ }
+
+ flash()->success('Bot created successfully.');
+
+ $this->reset(['name', 'namespace', 'class', 'enabled', 'schedule']);
+ }
+
+ public function updatedSchedule($value)
+ {
+ try {
+ $this->cron_text = CronTranslator::translate($value);
+ $this->resetErrorBag('schedule');
+ } catch (CronParsingException $e) {
+ $this->cron_text = '';
+ }
+ }
+
+ public function render()
+ {
+ return view('livewire.create-edit-bot');
+ }
+}
diff --git a/app/Models/Bot.php b/app/Models/Bot.php
new file mode 100644
index 0000000..31462c4
--- /dev/null
+++ b/app/Models/Bot.php
@@ -0,0 +1,39 @@
+ 'array',
+ ];
+
+ public function nextDue(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ $cron = new CronExpression($this->schedule);
+ return $cron->getNextRunDate()->format('Y-m-d H:i:s');
+ }
+ );
+ }
+
+ public function cronToHuman(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => CronTranslator::translate($this->schedule));
+ }
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 452e6b6..a2e13ff 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -11,7 +11,9 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
- //
+ app()->singleton(\App\Services\BotService::class, function ($app) {
+ return new \App\Services\BotService();
+ });
}
/**
diff --git a/app/Services/BotService.php b/app/Services/BotService.php
new file mode 100644
index 0000000..62a4279
--- /dev/null
+++ b/app/Services/BotService.php
@@ -0,0 +1,33 @@
+get();
+
+ foreach ($bots as $bot) {
+ $cron = new CronExpression($bot->schedule);
+ if ($cron->isDue()) {
+ try {
+ $instance = app($bot->class, ['config' => $bot->config]);
+
+ if ($instance instanceof BotContract) {
+ $instance->run();
+ }
+ } catch (\Throwable $e) {
+ Log::error("Bot [{$bot->name}] failed: " . $e->getMessage());
+ }
+
+ Log::info("Bot [{$bot->name}] executed successfully.");
+ }
+ }
+ }
+}
diff --git a/composer.json b/composer.json
index 90b6887..f1e5f01 100644
--- a/composer.json
+++ b/composer.json
@@ -10,10 +10,13 @@
"license": "MIT",
"require": {
"php": "^8.2",
+ "dragonmantank/cron-expression": "^3.4",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"livewire/flux": "^2.1.1",
- "livewire/volt": "^1.7.0"
+ "livewire/volt": "^1.7.0",
+ "lorisleiva/cron-translator": "^0.4.6",
+ "yoeunes/toastr": "^3.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",
@@ -78,4 +81,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
-}
\ No newline at end of file
+}
diff --git a/composer.lock b/composer.lock
index dd7a632..59997ed 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "61eccc5dc89e08e92f1eea8940ff4dae",
+ "content-hash": "0e6fbc1b4cb99ff544ccd78faca75fd6",
"packages": [
{
"name": "brick/math",
@@ -2221,6 +2221,62 @@
},
"time": "2025-08-06T15:40:50+00:00"
},
+ {
+ "name": "lorisleiva/cron-translator",
+ "version": "v0.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/lorisleiva/cron-translator.git",
+ "reference": "082e3cd493de13bd5816bfdda484d07bb2326768"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/lorisleiva/cron-translator/zipball/082e3cd493de13bd5816bfdda484d07bb2326768",
+ "reference": "082e3cd493de13bd5816bfdda484d07bb2326768",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Lorisleiva\\CronTranslator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Loris LEIVA",
+ "email": "loris.leiva@gmail.com",
+ "homepage": "https://lorisleiva.com"
+ }
+ ],
+ "description": "Makes CRON expressions human-readable",
+ "homepage": "https://github.com/lorisleiva/cron-translator",
+ "keywords": [
+ "cron",
+ "expression",
+ "human"
+ ],
+ "support": {
+ "issues": "https://github.com/lorisleiva/cron-translator/issues",
+ "source": "https://github.com/lorisleiva/cron-translator/tree/v0.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/lorisleiva",
+ "type": "github"
+ }
+ ],
+ "time": "2025-06-23T10:51:20+00:00"
+ },
{
"name": "monolog/monolog",
"version": "3.9.0",
@@ -2725,6 +2781,294 @@
],
"time": "2025-05-08T08:14:37+00:00"
},
+ {
+ "name": "php-flasher/flasher",
+ "version": "v2.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-flasher/flasher.git",
+ "reference": "054a209515d2eb1bb72467023d8614a29b5d2e60"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-flasher/flasher/zipball/054a209515d2eb1bb72467023d8614a29b5d2e60",
+ "reference": "054a209515d2eb1bb72467023d8614a29b5d2e60",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "functions.php",
+ "helpers.php"
+ ],
+ "psr-4": {
+ "Flasher\\Prime\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Younes ENNAJI",
+ "email": "younes.ennaji.pro@gmail.com",
+ "homepage": "https://www.linkedin.com/in/younes--ennaji/",
+ "role": "Developer"
+ }
+ ],
+ "description": "The foundational PHP library for PHPFlasher, enabling the creation of framework-agnostic flash notifications. Ideal for building custom integrations or for use in PHP projects.",
+ "homepage": "https://php-flasher.io",
+ "keywords": [
+ "custom-integrations",
+ "flash-notifications",
+ "flasher-core",
+ "framework-agnostic",
+ "open-source",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/php-flasher/php-flasher/issues",
+ "source": "https://github.com/php-flasher/php-flasher"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/paypalme/yoeunes",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/yoeunes",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-21T20:05:00+00:00"
+ },
+ {
+ "name": "php-flasher/flasher-laravel",
+ "version": "v2.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-flasher/flasher-laravel.git",
+ "reference": "7097ab207d602db59b241206048c9884aadc5c06"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-flasher/flasher-laravel/zipball/7097ab207d602db59b241206048c9884aadc5c06",
+ "reference": "7097ab207d602db59b241206048c9884aadc5c06",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "^11.0|^12.0",
+ "php": ">=8.2",
+ "php-flasher/flasher": "^2.1.6"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "Flasher": "Flasher\\Laravel\\Facade\\Flasher"
+ },
+ "providers": [
+ "Flasher\\Laravel\\FlasherServiceProvider"
+ ]
+ },
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Flasher\\Laravel\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Younes ENNAJI",
+ "email": "younes.ennaji.pro@gmail.com",
+ "homepage": "https://www.linkedin.com/in/younes--ennaji/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Seamlessly integrate flash notifications into your Laravel applications with PHPFlasher. Enhance user feedback and engagement with minimal setup.",
+ "homepage": "https://php-flasher.io",
+ "keywords": [
+ "flash-notifications",
+ "laravel",
+ "open-source",
+ "php",
+ "phpflasher",
+ "user-feedback"
+ ],
+ "support": {
+ "issues": "https://github.com/php-flasher/php-flasher/issues",
+ "source": "https://github.com/php-flasher/php-flasher"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/paypalme/yoeunes",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/yoeunes",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-21T20:05:00+00:00"
+ },
+ {
+ "name": "php-flasher/flasher-toastr",
+ "version": "v2.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-flasher/flasher-toastr.git",
+ "reference": "e9772b7d4e7a2780e41caf0e2f142e3c1670358f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-flasher/flasher-toastr/zipball/e9772b7d4e7a2780e41caf0e2f142e3c1670358f",
+ "reference": "e9772b7d4e7a2780e41caf0e2f142e3c1670358f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "php-flasher/flasher": "^2.1.6"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "functions.php",
+ "helpers.php"
+ ],
+ "psr-4": {
+ "Flasher\\Toastr\\Prime\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Younes ENNAJI",
+ "email": "younes.ennaji.pro@gmail.com",
+ "homepage": "https://www.linkedin.com/in/younes--ennaji/",
+ "role": "Developer"
+ }
+ ],
+ "description": "PHPFlasher Toastr - An extension for PHPFlasher to integrate Toastr notifications seamlessly in Laravel or Symfony. Enhances UX with elegant, responsive toast messages.",
+ "homepage": "https://php-flasher.io",
+ "keywords": [
+ "customizable-alerts-php",
+ "flash-messages",
+ "interactive-web-notifications",
+ "laravel-notification",
+ "php-messaging-library",
+ "php-notification-system",
+ "php-user-interface",
+ "symfony-notification",
+ "user-engagement-php",
+ "user-feedback-tools",
+ "web-application-notifications"
+ ],
+ "support": {
+ "issues": "https://github.com/php-flasher/php-flasher/issues",
+ "source": "https://github.com/php-flasher/php-flasher"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/paypalme/yoeunes",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/yoeunes",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-21T20:05:00+00:00"
+ },
+ {
+ "name": "php-flasher/flasher-toastr-laravel",
+ "version": "v2.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-flasher/flasher-toastr-laravel.git",
+ "reference": "db6c6d98405643236c47735b5352ee41f4e4691e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-flasher/flasher-toastr-laravel/zipball/db6c6d98405643236c47735b5352ee41f4e4691e",
+ "reference": "db6c6d98405643236c47735b5352ee41f4e4691e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "php-flasher/flasher-laravel": "^2.1.6",
+ "php-flasher/flasher-toastr": "^2.1.6"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "Toastr": "Flasher\\Toastr\\Laravel\\Facade\\Toastr"
+ },
+ "providers": [
+ "Flasher\\Toastr\\Laravel\\FlasherToastrServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Flasher\\Toastr\\Laravel\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Younes ENNAJI",
+ "email": "younes.ennaji.pro@gmail.com",
+ "homepage": "https://www.linkedin.com/in/younes--ennaji/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Leverage Toastr for stylish toast notifications in Laravel with PHPFlasher. Add Toastr's sleek notifications to your Laravel projects effortlessly.",
+ "homepage": "https://php-flasher.io",
+ "keywords": [
+ "dynamic-notifications",
+ "flash-notifications",
+ "laravel",
+ "open-source",
+ "phpflasher",
+ "toastr",
+ "user-feedback"
+ ],
+ "support": {
+ "issues": "https://github.com/php-flasher/php-flasher/issues",
+ "source": "https://github.com/php-flasher/php-flasher"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/paypalme/yoeunes",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/yoeunes",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-21T20:05:00+00:00"
+ },
{
"name": "phpoption/phpoption",
"version": "1.9.4",
@@ -6236,6 +6580,67 @@
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
},
"time": "2022-06-03T18:03:27+00:00"
+ },
+ {
+ "name": "yoeunes/toastr",
+ "version": "v3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yoeunes/toastr.git",
+ "reference": "0e203a03c3144dcd72c913ee845970f118e65fa2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yoeunes/toastr/zipball/0e203a03c3144dcd72c913ee845970f118e65fa2",
+ "reference": "0e203a03c3144dcd72c913ee845970f118e65fa2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "php-flasher/flasher-toastr-laravel": "^2.1.6"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Younes ENNAJI",
+ "email": "younes.ennaji.pro@gmail.com",
+ "homepage": "https://www.linkedin.com/in/younes--ennaji/",
+ "role": "Developer"
+ }
+ ],
+ "description": "toastr.js flush notifications for Laravel",
+ "homepage": "https://github.com/yoeunes/toastr",
+ "keywords": [
+ "dynamic-notifications",
+ "flash-notifications",
+ "laravel",
+ "open-source",
+ "phpflasher",
+ "toastr",
+ "user-feedback"
+ ],
+ "support": {
+ "docs": "https://php-flasher.io",
+ "email": "younes.ennaji.pro@gmail.com",
+ "issues": "https://github.com/php-flasher/php-flasher/issues",
+ "source": "https://github.com/php-flasher/php-flasher"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/paypalme/yoeunes",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/yoeunes",
+ "type": "github"
+ }
+ ],
+ "abandoned": "php-flasher/flasher-toastr-laravel",
+ "time": "2025-02-26T14:13:03+00:00"
}
],
"packages-dev": [
@@ -9535,12 +9940,12 @@
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^8.2"
},
- "platform-dev": [],
- "plugin-api-version": "2.2.0"
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
}
diff --git a/config/logging.php b/config/logging.php
index 8d94292..2c583e8 100644
--- a/config/logging.php
+++ b/config/logging.php
@@ -59,7 +59,7 @@ return [
],
'single' => [
- 'driver' => 'single',
+ 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
diff --git a/config/scheduler.php b/config/scheduler.php
new file mode 100644
index 0000000..ceb76e8
--- /dev/null
+++ b/config/scheduler.php
@@ -0,0 +1,8 @@
+ [
+ 'server_url' => env('MATTERMOST_SERVER_URL', 'https://chat.example.com'),
+ 'access_token' => env('MATTERMOST_ACCESS_TOKEN', 'your_access_token'),
+ ]
+];
diff --git a/database/migrations/2025_08_30_101757_create_bots_table.php b/database/migrations/2025_08_30_101757_create_bots_table.php
new file mode 100644
index 0000000..d5da527
--- /dev/null
+++ b/database/migrations/2025_08_30_101757_create_bots_table.php
@@ -0,0 +1,32 @@
+id();
+ $table->string('name');
+ $table->string('class');
+ $table->json('config')->nullable();
+ $table->string('schedule')->default('* * * * *');
+ $table->boolean('enabled')->default(true);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('bots');
+ }
+};
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..5478cff
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,56 @@
+services:
+ laravel.test:
+ build:
+ context: './vendor/laravel/sail/runtimes/8.4'
+ dockerfile: Dockerfile
+ args:
+ WWWGROUP: '${WWWGROUP}'
+ image: 'sail-8.4/app'
+ extra_hosts:
+ - 'host.docker.internal:host-gateway'
+ ports:
+ - '${APP_PORT:-80}:80'
+ - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
+ environment:
+ WWWUSER: '${WWWUSER}'
+ LARAVEL_SAIL: 1
+ XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
+ XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
+ IGNITION_LOCAL_SITES_PATH: '${PWD}'
+ volumes:
+ - '.:/var/www/html'
+ networks:
+ - sail
+ depends_on:
+ - mysql
+ mysql:
+ image: 'mysql/mysql-server:8.0'
+ ports:
+ - '${FORWARD_DB_PORT:-3306}:3306'
+ environment:
+ MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
+ MYSQL_ROOT_HOST: '%'
+ MYSQL_DATABASE: '${DB_DATABASE}'
+ MYSQL_USER: '${DB_USERNAME}'
+ MYSQL_PASSWORD: '${DB_PASSWORD}'
+ MYSQL_ALLOW_EMPTY_PASSWORD: 1
+ MYSQL_EXTRA_OPTIONS: '${MYSQL_EXTRA_OPTIONS}'
+ volumes:
+ - 'sail-mysql:/var/lib/mysql'
+ - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
+ networks:
+ - sail
+ healthcheck:
+ test:
+ - CMD
+ - mysqladmin
+ - ping
+ - '-p${DB_PASSWORD}'
+ retries: 3
+ timeout: 5s
+networks:
+ sail:
+ driver: bridge
+volumes:
+ sail-mysql:
+ driver: local
diff --git a/package-lock.json b/package-lock.json
index 9004201..7b6e205 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "scheduler",
+ "name": "html",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -10,6 +10,7 @@
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0",
+ "prettier-plugin-blade": "^2.1.21",
"tailwindcss": "^4.0.7",
"vite": "^7.0.4"
},
@@ -2053,6 +2054,34 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-blade": {
+ "version": "2.1.21",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-blade/-/prettier-plugin-blade-2.1.21.tgz",
+ "integrity": "sha512-+BPBPvla/Ppr0MVrqMAO+FTwxpXUYo8zhQPIGC7psNuMbB24y84cGrJ4Uc02GHTQN0q8txeG4Y4MxyJWgOujyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "prettier": ">=3"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
diff --git a/package.json b/package.json
index 963c417..70f18a5 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0",
+ "prettier-plugin-blade": "^2.1.21",
"tailwindcss": "^4.0.7",
"vite": "^7.0.4"
},
diff --git a/phpunit.xml b/phpunit.xml
index 61c031c..c09b5bc 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -22,8 +22,7 @@
-
-
+
diff --git a/public/vendor/flasher/flasher-toastr.min.js b/public/vendor/flasher/flasher-toastr.min.js
new file mode 100644
index 0000000..06f5c9b
--- /dev/null
+++ b/public/vendor/flasher/flasher-toastr.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("toastr")):"function"==typeof define&&define.amd?define(["@flasher/flasher","toastr"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).toastr=t(e.flasher,e.toastr)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,r){if("object"==typeof e?(e=(r=e).type,t=r.message,s=r.title):"object"==typeof t?(t=(r=t).message,s=r.title):"object"==typeof s&&(s=(r=s).title),void 0===t)throw new Error("message option is required");const o={type:e,message:t,title:s||e,options:r||{},metadata:{plugin:""}};this.renderOptions(r||{}),this.renderEnvelopes([o])}}const r=new class extends s{renderEnvelopes(e){e.forEach((e=>{const{message:s,title:r,type:o,options:i}=e,n=t[o](s,r,i);n&&n.parent().attr("data-turbo-temporary","")}))}renderOptions(e){t.options=Object.assign({timeOut:e.timeOut||5e3,progressBar:e.progressBar||!0},e)}};return e.addPlugin("toastr",r),r}));
diff --git a/public/vendor/flasher/flasher.min.css b/public/vendor/flasher/flasher.min.css
new file mode 100644
index 0000000..188be96
--- /dev/null
+++ b/public/vendor/flasher/flasher.min.css
@@ -0,0 +1,2 @@
+:root{--background-color:#fff;--text-color:#4b5563;--dark-background-color:#0f172a;--dark-text-color:#fff;--success-color:#10b981;--info-color:#3b82f6;--warning-color:#f59e0b;--error-color:#ef4444;--success-color-light:#d4f7eb;--info-color-light:#d4e1f7;--warning-color-light:#fce8cf;--error-color-light:#f9d2d2}.fl-wrapper{position:fixed;-webkit-transition:all 1s ease-in-out;-moz-transition:all 1s ease-in-out;transition:all 1s ease-in-out;width:24em;z-index:10}@media only screen and (width <= 480px){.fl-wrapper{left:5%;right:5%;width:90%}}.fl-wrapper[data-position^=top-]{top:.5em}.fl-wrapper[data-position^=bottom-]{bottom:.5em}.fl-wrapper[data-position$=-right]{right:.5em}.fl-wrapper[data-position$=-right] .fl-container{-webkit-transform:translateX(110%);-moz-transform:translateX(110%);-ms-transform:translateX(110%);transform:translateX(110%)}.fl-wrapper[data-position$=-left]{left:.5em}.fl-wrapper[data-position$=-left] .fl-container{-webkit-transform:translateX(-110%);-moz-transform:translateX(-110%);-ms-transform:translateX(-110%);transform:translateX(-110%)}.fl-wrapper[data-position$=-center]{left:50%;-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.fl-wrapper[data-position=top-center] .fl-container{-webkit-transform:translateY(-100vh);-moz-transform:translateY(-100vh);-ms-transform:translateY(-100vh);transform:translateY(-100vh)}.fl-wrapper[data-position=bottom-center] .fl-container{-webkit-transform:translateY(100vh);-moz-transform:translateY(100vh);-ms-transform:translateY(100vh);transform:translateY(100vh)}.fl-container{color:var(--text-color);opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px);-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;transition:all .5s ease-in-out}.fl-container.fl-show{opacity:1;-webkit-transform:translate(0)!important;-moz-transform:translate(0)!important;-ms-transform:translate(0)!important;transform:translate(0)!important}.fl-container.fl-rtl{direction:rtl}.fl-icon{border-radius:50%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:#fff;display:inline-block;height:1em;margin:0;min-height:1em;min-width:1em;position:relative;-webkit-transition:all 1s;-moz-transition:all 1s;transition:all 1s;width:1em}.fl-icon:after,.fl-icon:before{border-width:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;content:"";position:absolute;-webkit-transition:all 1s;-moz-transition:all 1s;transition:all 1s}.fl-success .fl-icon:after,.fl-success .fl-icon:before{background-color:currentcolor;border-radius:.1em;height:.6em;left:.35em;top:.6em;-webkit-transform:rotate(-135deg);-moz-transform:rotate(-135deg);-ms-transform:rotate(-135deg);transform:rotate(-135deg);-webkit-transform-origin:.08em .08em;-moz-transform-origin:.08em .08em;-ms-transform-origin:.08em .08em;transform-origin:.08em .08em;width:.16em}.fl-success .fl-icon:after{height:.16em;width:.4em}.fl-info .fl-icon:after,.fl-info .fl-icon:before{background-color:currentcolor;border-radius:.03em;left:50%;-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);width:.15em}.fl-info .fl-icon:before{height:.38em;top:.4em}.fl-info .fl-icon:after{-webkit-box-shadow:-.06em .19em,-.06em .44em,.06em .44em;box-shadow:-.06em .19em,-.06em .44em,.06em .44em;height:.13em;top:.21em}.fl-warning .fl-icon:after,.fl-warning .fl-icon:before{background-color:currentcolor;border-radius:.03em;left:50%;-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);width:.15em}.fl-warning .fl-icon:before{height:.38em;top:.21em}.fl-warning .fl-icon:after{height:.13em;top:.65em}.fl-error .fl-icon:after,.fl-error .fl-icon:before{background-color:currentcolor;border-radius:.1em;height:.7em;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) rotate(-135deg);-moz-transform:translate(-50%,-50%) rotate(-135deg);-ms-transform:translate(-50%,-50%) rotate(-135deg);transform:translate(-50%,-50%) rotate(-135deg);width:.16em}.fl-error .fl-icon:after{-webkit-transform:translate(-50%,-50%) rotate(-45deg);-moz-transform:translate(-50%,-50%) rotate(-45deg);-ms-transform:translate(-50%,-50%) rotate(-45deg);transform:translate(-50%,-50%) rotate(-45deg)}.fl-success .fl-icon{background-color:var(--success-color)}.fl-info .fl-icon{background-color:var(--info-color)}.fl-warning .fl-icon{background-color:var(--warning-color)}.fl-error .fl-icon{background-color:var(--error-color)}.fl-progress-bar{bottom:0;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:flex;height:.125em;left:0;position:absolute;right:0}.fl-success .fl-progress-bar{background-color:var(--success-color-light)}.fl-success .fl-progress-bar .fl-progress{background-color:var(--success-color)}.fl-info .fl-progress-bar{background-color:var(--info-color-light)}.fl-info .fl-progress-bar .fl-progress{background-color:var(--info-color)}.fl-warning .fl-progress-bar{background-color:var(--warning-color-light)}.fl-warning .fl-progress-bar .fl-progress{background-color:var(--warning-color)}.fl-error .fl-progress-bar{background-color:var(--error-color-light)}.fl-error .fl-progress-bar .fl-progress{background-color:var(--error-color)}
+body.fl-dark .fl-flasher,html.fl-dark .fl-flasher{--background-color:var(--dark-background-color);--text-color:var(--dark-text-color)}.fl-flasher{background-color:var(--background-color);border-bottom:none;-webkit-box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);color:var(--text-color);line-height:1.5;margin:.75em 0;padding:.75em;position:relative;word-break:break-word}.fl-flasher.fl-rtl{border-radius:0 .375em .375em 0}.fl-flasher:not(.fl-rtl){border-radius:.375em 0 0 .375em}.fl-flasher .fl-content{-webkit-box-align:center;-webkit-align-items:center;-moz-box-align:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:flex}.fl-flasher .fl-icon{font-size:2.5em}.fl-flasher .fl-message,.fl-flasher .fl-title{display:block;line-height:1.25em;margin-left:1em;margin-right:1em}.fl-flasher .fl-title{font-size:1em;font-weight:700}.fl-flasher .fl-message{font-size:.875em;margin-top:.25em}.fl-flasher .fl-close{-webkit-box-align:center;-webkit-align-items:center;-moz-box-align:center;align-items:center;background-color:transparent;border:none;color:#a8aaab;cursor:pointer;display:-webkit-inline-box;display:-webkit-inline-flex;display:-moz-inline-box;display:inline-flex;font-size:25px;-webkit-box-pack:center;-webkit-justify-content:center;-moz-box-pack:center;justify-content:center;line-height:0;margin:-.5rem;padding:.5rem;position:absolute;right:.5rem;top:1rem;-webkit-transition:color .3s ease,-webkit-transform .3s ease;transition:color .3s ease,-webkit-transform .3s ease;-moz-transition:color .3s ease,transform .3s ease,-moz-transform .3s ease;transition:color .3s ease,transform .3s ease;transition:color .3s ease,transform .3s ease,-webkit-transform .3s ease,-moz-transform .3s ease}.fl-flasher .fl-close:hover{color:#8e9192;-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}.fl-flasher.fl-rtl .fl-close{left:.5rem;right:auto}.fl-flasher.fl-success{border-left:.8em solid var(--success-color)}.fl-flasher.fl-success.fl-rtl{border-left:none;border-right:.8em solid var(--success-color)}.fl-flasher.fl-success:not(.fl-rtl){border-left:.8em solid var(--success-color);border-right:none}.fl-flasher.fl-success .fl-title{color:var(--success-color)}.fl-flasher.fl-info{border-left:.8em solid var(--info-color)}.fl-flasher.fl-info.fl-rtl{border-left:none;border-right:.8em solid var(--info-color)}.fl-flasher.fl-info:not(.fl-rtl){border-left:.8em solid var(--info-color);border-right:none}.fl-flasher.fl-info .fl-title{color:var(--info-color)}.fl-flasher.fl-warning{border-left:.8em solid var(--warning-color)}.fl-flasher.fl-warning.fl-rtl{border-left:none;border-right:.8em solid var(--warning-color)}.fl-flasher.fl-warning:not(.fl-rtl){border-left:.8em solid var(--warning-color);border-right:none}.fl-flasher.fl-warning .fl-title{color:var(--warning-color)}.fl-flasher.fl-error{border-left:.8em solid var(--error-color)}.fl-flasher.fl-error.fl-rtl{border-left:none;border-right:.8em solid var(--error-color)}.fl-flasher.fl-error:not(.fl-rtl){border-left:.8em solid var(--error-color);border-right:none}.fl-flasher.fl-error .fl-title{color:var(--error-color)}
\ No newline at end of file
diff --git a/public/vendor/flasher/flasher.min.js b/public/vendor/flasher/flasher.min.js
new file mode 100644
index 0000000..2804f95
--- /dev/null
+++ b/public/vendor/flasher/flasher.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).flasher=t()}(this,(function(){"use strict";function e(e,t,s,n){return new(s||(s=Promise))((function(o,i){function r(e){try{a(n.next(e))}catch(e){i(e)}}function l(e){try{a(n.throw(e))}catch(e){i(e)}}function a(e){var t;e.done?o(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(r,l)}a((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,n){if("object"==typeof e?(e=(n=e).type,t=n.message,s=n.title):"object"==typeof t?(t=(n=t).message,s=n.title):"object"==typeof s&&(s=(n=s).title),void 0===t)throw new Error("message option is required");const o={type:e,message:t,title:s||e,options:n||{},metadata:{plugin:""}};this.renderOptions(n||{}),this.renderEnvelopes([o])}}class s extends t{constructor(e){super(),this.options={timeout:null,timeouts:{success:5e3,info:5e3,error:5e3,warning:5e3},fps:30,position:"top-right",direction:"top",rtl:!1,style:{},escapeHtml:!1},this.theme=e}renderEnvelopes(e){const t=()=>e.forEach((e=>{var t,s,n,o;const i=null!==(s=null!==(t=this.options.timeout)&&void 0!==t?t:this.options.timeouts[e.type])&&void 0!==s?s:5e3,r=Object.assign(Object.assign(Object.assign({},this.options),e.options),{timeout:null!==(n=e.options.timeout)&&void 0!==n?n:i,escapeHtml:null!==(o=e.options.escapeHtml)&&void 0!==o?o:this.options.escapeHtml});this.addToContainer(this.createContainer(r),e,r)}));"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}renderOptions(e){this.options=Object.assign(Object.assign({},this.options),e)}createContainer(e){let t=document.querySelector(`.fl-wrapper[data-position="${e.position}"]`);return t||(t=document.createElement("div"),t.className="fl-wrapper",t.dataset.position=e.position,Object.entries(e.style).forEach((([e,s])=>t.style.setProperty(e,s))),document.body.appendChild(t)),t.dataset.turboTemporary="",t}addToContainer(e,t,s){var n;s.escapeHtml&&(t.title=this.escapeHtml(t.title),t.message=this.escapeHtml(t.message));const o=this.stringToHTML(this.theme.render(t));o.classList.add(...("fl-container"+(s.rtl?" fl-rtl":"")).split(" ")),"bottom"===s.direction?e.append(o):e.prepend(o),requestAnimationFrame((()=>o.classList.add("fl-show"))),null===(n=o.querySelector(".fl-close"))||void 0===n||n.addEventListener("click",(e=>{e.stopPropagation(),this.removeNotification(o)})),this.addProgressBar(o,s)}addProgressBar(e,{timeout:t,fps:s}){if(t<=0||s<=0)return;const n=e.querySelector(".fl-progress-bar");if(!n)return;const o=document.createElement("span");o.classList.add("fl-progress"),n.append(o);const i=1e3/s;let r=0;const l=()=>{r+=1;const s=100*(1-i*(r/t));o.style.width=`${s}%`,s<=0&&(clearInterval(a),this.removeNotification(e))};let a=window.setInterval(l,i);e.addEventListener("mouseout",(()=>a=window.setInterval(l,i))),e.addEventListener("mouseover",(()=>clearInterval(a)))}removeNotification(e){e.classList.remove("fl-show"),e.ontransitionend=()=>{var t,s;!(null===(t=e.parentElement)||void 0===t?void 0:t.hasChildNodes())&&(null===(s=e.parentElement)||void 0===s||s.remove()),e.remove()}}stringToHTML(e){const t=document.createElement("template");return t.innerHTML=e.trim(),t.content.firstElementChild}escapeHtml(e){return null==e?"":e.replace(/[&<>"'`=\/]/g,(e=>({"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"=","/":"/"}[e])))}}const n=new class extends t{constructor(){super(...arguments),this.defaultPlugin="flasher",this.plugins=new Map,this.themes=new Map}render(t){return e(this,void 0,void 0,(function*(){const e=this.resolveResponse(t);yield this.addAssets([{urls:e.styles,nonce:e.context.csp_style_nonce,type:"style"},{urls:e.scripts,nonce:e.context.csp_script_nonce,type:"script"}]),this.renderOptions(e.options),this.renderEnvelopes(e.envelopes)}))}renderEnvelopes(e){const t={};e.forEach((e=>{const s=this.resolvePluginAlias(e.metadata.plugin);t[s]=t[s]||[],t[s].push(e)})),Object.entries(t).forEach((([e,t])=>{this.use(e).renderEnvelopes(t)}))}renderOptions(e){Object.entries(e).forEach((([e,t])=>{this.use(e).renderOptions(t)}))}addPlugin(e,t){this.plugins.set(e,t)}addTheme(e,t){this.themes.set(e,t)}use(e){e=this.resolvePluginAlias(e),this.resolvePlugin(e);const t=this.plugins.get(e);if(!t)throw new Error(`Unable to resolve "${e}" plugin, did you forget to register it?`);return t}create(e){return this.use(e)}resolveResponse(e){const t=Object.assign({envelopes:[],options:{},scripts:[],styles:[],context:{}},e);return Object.entries(t.options).forEach((([e,s])=>{t.options[e]=this.resolveOptions(s)})),t.context.csp_style_nonce=t.context.csp_style_nonce||"",t.context.csp_script_nonce=t.context.csp_script_nonce||"",t.envelopes.forEach((s=>{s.metadata=s.metadata||{},s.metadata.plugin=this.resolvePluginAlias(s.metadata.plugin),this.addThemeStyles(t,s.metadata.plugin),s.options=this.resolveOptions(s.options),s.context=e.context})),t}resolveOptions(e){return Object.entries(e).forEach((([t,s])=>{e[t]=this.resolveFunction(s)})),e}resolveFunction(e){var t,s;if("string"!=typeof e)return e;const n=e.match(/^function\s*(\w*)\s*\(([^)]*)\)\s*\{([\s\S]*)\}$/)||e.match(/^\s*(\(([^)]*)\)|[^=]+)\s*=>\s*([\s\S]+)$/);if(!n)return e;const o=null!==(s=null===(t=n[2])||void 0===t?void 0:t.split(",").map((e=>e.trim())))&&void 0!==s?s:[];let i=n[3].trim();i.startsWith("{")||(i=`{ return ${i}; }`);try{return new Function(...o,i)}catch(t){return console.error("Error converting string to function:",t),e}}resolvePlugin(e){if(this.plugins.get(e)||!e.includes("theme."))return;const t=this.themes.get(e.replace("theme.",""));t&&this.addPlugin(e,new s(t))}resolvePluginAlias(e){return"flasher"===(e=e||this.defaultPlugin)?"theme.flasher":e}addAssets(t){return e(this,void 0,void 0,(function*(){for(const{urls:e,nonce:s,type:n}of t)for(const t of e)yield this.loadAsset(t,s,n)}))}loadAsset(t,s,n){return e(this,void 0,void 0,(function*(){if(document.querySelector(`${"style"===n?"link":"script"}[src="${t}"]`))return;const e=document.createElement("style"===n?"link":"script");return"style"===n?(e.rel="stylesheet",e.href=t):(e.type="text/javascript",e.src=t),s&&e.setAttribute("nonce",s),document.head.appendChild(e),new Promise(((s,n)=>{e.onload=()=>s(),e.onerror=()=>n(new Error(`Failed to load ${t}`))}))}))}addThemeStyles(e,t){var s;if("flasher"!==t&&!t.includes("theme."))return;t=t.replace("theme.","");const n=(null===(s=this.themes.get(t))||void 0===s?void 0:s.styles)||[];e.styles=Array.from(new Set([...e.styles,...n]))}};return n.addTheme("flasher",{render:e=>{const{type:t,title:s,message:n}=e,o="error"===t||"warning"===t;return`\n
\n
\n
\n
\n ${s}\n ${n}\n
\n
\n
\n
\n
`}}),n}));
diff --git a/public/vendor/flasher/jquery.min.js b/public/vendor/flasher/jquery.min.js
new file mode 100644
index 0000000..7f37b5d
--- /dev/null
+++ b/public/vendor/flasher/jquery.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}
\ No newline at end of file
diff --git a/public/vendor/flasher/toastr.min.js b/public/vendor/flasher/toastr.min.js
new file mode 100644
index 0000000..06e4814
--- /dev/null
+++ b/public/vendor/flasher/toastr.min.js
@@ -0,0 +1,2 @@
+!function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e(""),M=e(""),B=e(""),q=e(""),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.4",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)});
+//# sourceMappingURL=toastr.js.map
diff --git a/resources/views/components/app-logo.blade.php b/resources/views/components/app-logo.blade.php
index 2ccc081..47a7c38 100644
--- a/resources/views/components/app-logo.blade.php
+++ b/resources/views/components/app-logo.blade.php
@@ -1,6 +1,3 @@
-
-
-
- Laravel Starter Kit
+ Scheduler
diff --git a/resources/views/components/layouts/app/sidebar.blade.php b/resources/views/components/layouts/app/sidebar.blade.php
index 8dd5882..4f9277d 100644
--- a/resources/views/components/layouts/app/sidebar.blade.php
+++ b/resources/views/components/layouts/app/sidebar.blade.php
@@ -19,16 +19,6 @@
-
-
- {{ __('Repository') }}
-
-
-
- {{ __('Documentation') }}
-
-
-
-
-
-
-
- {{ config('app.name', 'Laravel') }}
-
diff --git a/resources/views/components/layouts/auth/simple.blade.php b/resources/views/components/layouts/auth/simple.blade.php
index 6e0d909..337aa96 100644
--- a/resources/views/components/layouts/auth/simple.blade.php
+++ b/resources/views/components/layouts/auth/simple.blade.php
@@ -6,12 +6,7 @@
-
-
-
-
- {{ config('app.name', 'Laravel') }}
-
+
{{ $slot }}
diff --git a/resources/views/components/layouts/auth/split.blade.php b/resources/views/components/layouts/auth/split.blade.php
index 4e9788b..072669c 100644
--- a/resources/views/components/layouts/auth/split.blade.php
+++ b/resources/views/components/layouts/auth/split.blade.php
@@ -27,13 +27,7 @@
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
index 5b4558d..29bfc74 100644
--- a/resources/views/dashboard.blade.php
+++ b/resources/views/dashboard.blade.php
@@ -1,18 +1,40 @@
+
-
-
+
-
-
+
-
-
-
+
+
+
All Bots
+
+
+ @livewire('bots-list')
+
diff --git a/resources/views/livewire/auth/login.blade.php b/resources/views/livewire/auth/login.blade.php
index 50be1ba..b23780d 100644
--- a/resources/views/livewire/auth/login.blade.php
+++ b/resources/views/livewire/auth/login.blade.php
@@ -1,5 +1,4 @@
-
@@ -42,11 +41,4 @@
{{ __('Log in') }}
-
- @if (Route::has('register'))
-
- {{ __('Don\'t have an account?') }}
- {{ __('Sign up') }}
-
- @endif
diff --git a/resources/views/livewire/bots-list.blade.php b/resources/views/livewire/bots-list.blade.php
new file mode 100644
index 0000000..fb90eaf
--- /dev/null
+++ b/resources/views/livewire/bots-list.blade.php
@@ -0,0 +1,39 @@
+
diff --git a/resources/views/livewire/create-edit-bot.blade.php b/resources/views/livewire/create-edit-bot.blade.php
new file mode 100644
index 0000000..fa1b7da
--- /dev/null
+++ b/resources/views/livewire/create-edit-bot.blade.php
@@ -0,0 +1,76 @@
+
+
+
+ @error('name')
+ {{ $message }}
+ @enderror
+
+
+
+
+ @error('bot')
+ {{ $message }}
+ @enderror
+
+
+
+
+
+ {{ $cron_text }}
+
+ @error('schedule')
+ {{ $message }}
+ @enderror
+
+
+
+
Config
+ @forelse ($configSchema as $field => $meta)
+
+ config[$field] ?? '') }}"
+ />
+ @error('config.' . $field)
+ {{ $message }}
+ @enderror
+ @empty
+ Select a bot first
+ @endforelse
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php
index a808a39..070bdfd 100644
--- a/resources/views/welcome.blade.php
+++ b/resources/views/welcome.blade.php
@@ -23,7 +23,7 @@
@if (Route::has('login'))
-
-
-
-
-
Let's get started
-
Laravel has an incredibly rich ecosystem.
We suggest starting with the following.
-
-
-
-
- {{-- Laravel Logo --}}
-
- {{-- Light Mode 12 SVG --}}
-
-
- {{-- Dark Mode 12 SVG --}}
-
-
-
-
-
@if (Route::has('login'))
diff --git a/routes/console.php b/routes/console.php
index 3c9adf1..742b06a 100644
--- a/routes/console.php
+++ b/routes/console.php
@@ -1,8 +1,13 @@
comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
+
+Schedule::call(fn() => app(BotService::class)->run())->everyMinute();
+
diff --git a/routes/web.php b/routes/web.php
index 910db32..2522cde 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,5 +1,8 @@
name('home');
-Route::view('dashboard', 'dashboard')
+Route::get('dashboard', [App\Http\Controllers\DashboardController::class, 'index'])
->middleware(['auth', 'verified'])
->name('dashboard');
Route::middleware(['auth'])->group(function () {
+ Route::get('bots/create', CreateEditBot::class)
+ ->name('bots.create');
+ Route::get('bots/edit/{bot}', CreateEditBot::class)
+ ->name('bots.edit');
+
Route::redirect('settings', 'settings/profile');
Route::get('settings/profile', Profile::class)->name('settings.profile');
diff --git a/vite.config.js b/vite.config.js
index 75a8c16..dd9e2e4 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -15,4 +15,4 @@ export default defineConfig({
server: {
cors: true,
},
-});
\ No newline at end of file
+});