70 Commits

Author SHA1 Message Date
Ninluc e8b9517664 Added Hellcase Battle job
Push image to registry / build-image (push) Successful in 5m59s
Still need testing and making proper notifications
2025-03-18 19:40:55 +01:00
Ninluc cfbae6ddbf Only render jobs run if there is at least one
Push image to registry / build-image (push) Successful in 4m10s
2025-03-16 17:37:39 +01:00
Ninluc 4368aae6c4 install missing dependencies
Push image to registry / build-image (push) Successful in 5m25s
2025-03-15 17:49:53 +01:00
Ninluc 070235e011 Added jobRuns on the job page
Push image to registry / build-image (push) Failing after 3m47s
2025-03-15 17:44:30 +01:00
Ninluc ad10dcaa0f Fix joining giveaways 2025-03-15 16:26:29 +01:00
Ninluc 63216ae7e8 Fix pls
Push image to registry / build-image (push) Successful in 8m20s
2025-03-11 21:04:53 +01:00
Ninluc 4a68c1d223 Fix signin ?
Push image to registry / build-image (push) Successful in 6m33s
2025-03-11 20:38:15 +01:00
Ninluc d48c0cd148 Try to fix login
Push image to registry / build-image (push) Successful in 6m32s
2025-03-11 20:25:00 +01:00
Ninluc 8e356ac1ef Click the next button
Push image to registry / build-image (push) Successful in 8m26s
2025-03-11 19:33:44 +01:00
Ninluc a693339f97 Get daily free even if giveaway fails
Push image to registry / build-image (push) Successful in 6m51s
2025-03-11 18:46:45 +01:00
Ninluc 5bfaba4a8e try to fix unclickable giveway button
Push image to registry / build-image (push) Has been cancelled
2025-03-11 18:45:54 +01:00
Ninluc 140966fb6b fix should start maximized ?
Push image to registry / build-image (push) Successful in 5m28s
2025-03-08 15:43:22 +01:00
Ninluc e3443ca632 update composer lock
Push image to registry / build-image (push) Successful in 5m13s
2025-03-02 13:51:44 +01:00
Ninluc 17af60471b Fix Notifications
Push image to registry / build-image (push) Failing after 4m19s
2025-03-02 13:31:26 +01:00
Ninluc 0014045d52 Fix job 2025-03-02 13:29:44 +01:00
Ninluc fec63ff249 Added Debug notifications
Push image to registry / build-image (push) Failing after 3m41s
2025-03-02 10:03:27 +01:00
Ninluc cdcdcd9e8b Wait for daily free if less than a minute
Push image to registry / build-image (push) Failing after 3m45s
2025-03-02 09:40:07 +01:00
Ninluc 599ef77d64 Should fix steam connexion
Push image to registry / build-image (push) Failing after 6m50s
2025-03-02 09:08:44 +01:00
Ninluc edb07dd960 Upgrade to Laravel 12
Push image to registry / build-image (push) Failing after 3m42s
2025-03-01 15:31:11 +01:00
Ninluc 6a95653c52 Added notification on job fail
Push image to registry / build-image (push) Successful in 5m20s
2025-03-01 15:12:15 +01:00
Ninluc 025711e09d Updated dockerignore
Push image to registry / build-image (push) Successful in 5m1s
2025-03-01 12:49:02 +01:00
Ninluc 5a30cdeae5 Made a script to patch latest selenium/standalone-chromedriver 2025-03-01 12:33:03 +01:00
Ninluc db9d65f445 Fix job ordering 2025-03-01 10:02:47 +01:00
Ninluc a1219b92ce Should fix
Push image to registry / build-image (push) Successful in 7m31s
2025-02-28 17:05:47 +01:00
Ninluc ce13d1b0dd Job qui enlève les vieilles jobRun
Push image to registry / build-image (push) Successful in 6m18s
2025-02-27 18:54:40 +01:00
Ninluc a80a32eee8 Added links to browser downloads and others
Push image to registry / build-image (push) Successful in 6m20s
2025-02-27 17:57:54 +01:00
Ninluc cd3491c5a8 Install reverb for broadcasting
Push image to registry / build-image (push) Successful in 3m56s
2025-02-26 18:52:12 +01:00
Ninluc 9e55c98d47 Added TODO list 2025-02-26 18:28:42 +01:00
Ninluc 7abfea14e6 Reschedule 24hr after getting dailyFree
Push image to registry / build-image (push) Successful in 3m53s
2025-02-26 18:25:20 +01:00
Ninluc b177b27390 FIx ot reschedule in one hour but in one minute
Push image to registry / build-image (push) Successful in 6m42s
2025-02-26 07:19:58 +01:00
Ninluc 8d814c7658 Try to fix reschedules in seconds
Push image to registry / build-image (push) Has been cancelled
2025-02-26 07:18:32 +01:00
Ninluc 56ec14f1da Move file 2025-02-24 18:58:22 +01:00
Ninluc c7ad5810a7 fix jobModel again
Push image to registry / build-image (push) Successful in 4m31s
2025-02-22 08:53:54 +01:00
Ninluc 5b939f7d34 Fix jobModel interfering
Push image to registry / build-image (push) Failing after 53s
2025-02-22 08:46:47 +01:00
Ninluc 540c41357e cron with daemon
Push image to registry / build-image (push) Failing after 1m0s
2025-02-22 08:35:03 +01:00
Ninluc 9f7603ea18 Only start the job if active 2025-02-22 08:34:47 +01:00
Ninluc e44ea5a3b2 Fix queue wotker
Push image to registry / build-image (push) Successful in 3m46s
2025-02-21 18:34:19 +01:00
Ninluc a4d52e3f88 Logging and only one job in the queue 2025-02-21 18:34:03 +01:00
Ninluc 750bcf13b4 Fix supervisor
Push image to registry / build-image (push) Successful in 3m46s
2025-02-20 20:22:04 +01:00
Ninluc 7a03225a21 Fix supervisor
Push image to registry / build-image (push) Successful in 6m46s
2025-02-20 20:00:25 +01:00
Ninluc 51eafce03a Changed queue worker
Push image to registry / build-image (push) Failing after 7m39s
2025-02-20 19:22:38 +01:00
Ninluc f1f32c44b5 Fix daily free screenshot
Push image to registry / build-image (push) Successful in 3m53s
2025-02-19 18:50:34 +01:00
Ninluc 7b51e29060 Don't filter anything
Push image to registry / build-image (push) Successful in 3m43s
2025-02-19 17:47:18 +01:00
Ninluc 9ca6ba3018 Telescope Prune
Push image to registry / build-image (push) Successful in 3m46s
2025-02-19 17:40:57 +01:00
Ninluc a05e5e224d Fix telescope not availible on prod
Push image to registry / build-image (push) Has been cancelled
2025-02-19 17:38:42 +01:00
Ninluc 61e55f960e Fix Job
Push image to registry / build-image (push) Successful in 4m41s
2025-02-18 18:48:48 +01:00
Ninluc 313c0a6a9a Fix queue 2025-02-18 18:48:32 +01:00
Ninluc 2fdd34bc35 Added failed_jobs table 2025-02-18 18:37:05 +01:00
Ninluc e1d7a19f6c Added Telescope 2025-02-18 18:36:55 +01:00
Ninluc 77968e7619 Fix page name 2025-02-16 10:24:47 +01:00
Ninluc e535a85058 Fix favicon
Push image to registry / build-image (push) Successful in 3m47s
2025-02-16 10:21:12 +01:00
Ninluc b40764c4ad Fix crontab
Push image to registry / build-image (push) Successful in 3m47s
2025-02-16 10:04:41 +01:00
Ninluc e6776df487 Fix login button
Push image to registry / build-image (push) Successful in 6m47s
2025-02-15 13:34:37 +01:00
Ninluc 7151c6adff Fix ?
Push image to registry / build-image (push) Successful in 2m4s
2025-02-08 17:59:01 +01:00
Ninluc 22283b3b10 Fix clicking signin with steam
Push image to registry / build-image (push) Successful in 2m7s
2025-02-08 17:54:44 +01:00
Ninluc fa674c3381 Update sign in with steam button
Push image to registry / build-image (push) Successful in 2m50s
2025-02-08 17:44:55 +01:00
Ninluc 8cb0e2f88a Clening up
Push image to registry / build-image (push) Successful in 3m55s
2025-02-08 11:26:10 +01:00
Ninluc 1c3c4b1257 Cache when running
Push image to registry / build-image (push) Successful in 3m46s
2025-02-08 11:16:40 +01:00
Ninluc 77aa2e85c5 idk
Push image to registry / build-image (push) Successful in 35s
2025-02-08 11:13:42 +01:00
Ninluc 9647676c27 Fix debug
Push image to registry / build-image (push) Successful in 3m53s
2025-02-08 11:00:30 +01:00
Ninluc 71ad103a1f debug
Push image to registry / build-image (push) Successful in 3m52s
2025-02-08 10:46:20 +01:00
Ninluc 990fcb9a0d Added rescheduling
Push image to registry / build-image (push) Successful in 6m12s
2025-02-08 10:36:19 +01:00
Ninluc e54d65ef8b Refix debug 2025-02-08 10:36:12 +01:00
Ninluc d4ea33e181 Fix debug
Push image to registry / build-image (push) Failing after 1m1s
2025-02-08 10:31:51 +01:00
Ninluc ec869bffe9 Debug
Push image to registry / build-image (push) Failing after 52s
2025-02-08 10:23:34 +01:00
Ninluc 7f4ba8a8c9 Wait for CASES text to be able to know the page loaded
Push image to registry / build-image (push) Successful in 3m44s
2025-02-08 10:13:02 +01:00
Ninluc 1c71b88d15 Force https only if APP_URL statrs with https
Push image to registry / build-image (push) Has been cancelled
2025-02-08 10:11:14 +01:00
Ninluc 2142ea9cc2 Force https
Push image to registry / build-image (push) Successful in 3m50s
2025-02-08 10:04:40 +01:00
Ninluc 99f866fbef Force it
Push image to registry / build-image (push) Successful in 3m45s
2025-02-07 16:20:05 +01:00
Ninluc 2be60c003b Revert vite config 2025-02-07 16:18:05 +01:00
83 changed files with 3284 additions and 276 deletions
+3
View File
@@ -14,6 +14,7 @@ database/database.sqlite
**/.dockerignore **/.dockerignore
**/.env **/.env
**/.git **/.git
.gitea/
**/.gitignore **/.gitignore
**/.project **/.project
**/.settings **/.settings
@@ -67,3 +68,5 @@ yarn-error.log
app/Browser/console/** app/Browser/console/**
app/Browser/screenshots/** app/Browser/screenshots/**
app/Browser/source/** app/Browser/source/**
undetectedChromedriver
+13
View File
@@ -68,3 +68,16 @@ VITE_APP_URL="${APP_URL}"
DUSK_DRIVER_URL="http://undetected-chromedriver:4444" DUSK_DRIVER_URL="http://undetected-chromedriver:4444"
DUSK_START_MAXIMIZED=true DUSK_START_MAXIMIZED=true
# Reverb (broadcasting)
REVERB_APP_ID=943562
REVERB_APP_KEY=jzysqfijn8wdgugrilbb
REVERB_APP_SECRET=gyotb3asui2zbrltd3ob
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
+13
View File
@@ -68,3 +68,16 @@ VITE_APP_URL="${APP_URL}"
DUSK_DRIVER_URL="http://127.0.0.1:4444" DUSK_DRIVER_URL="http://127.0.0.1:4444"
DUSK_START_MAXIMIZED=true DUSK_START_MAXIMIZED=true
# Reverb (broadcasting)
REVERB_APP_ID=943562
REVERB_APP_KEY=jzysqfijn8wdgugrilbb
REVERB_APP_SECRET=gyotb3asui2zbrltd3ob
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
+5
View File
@@ -21,6 +21,11 @@ yarn-error.log
/.nova /.nova
/.vscode /.vscode
/.zed /.zed
# Python projet
venv
__pycache__
# Browser # Browser
app/Browser/console app/Browser/console
app/Browser/screenshots app/Browser/screenshots
+14 -8
View File
@@ -38,6 +38,8 @@ RUN mkdir -p public/build/ && npm i && npm run build
# RUN # RUN
FROM php:8.2-alpine AS final FROM php:8.2-alpine AS final
ARG APP_ENV_FILE=.env.docker
# Install system dependencies # Install system dependencies
RUN apk update && apk add --no-cache \ RUN apk update && apk add --no-cache \
git \ git \
@@ -50,7 +52,9 @@ RUN apk update && apk add --no-cache \
supervisor \ supervisor \
nginx \ nginx \
openssl \ openssl \
linux-headers linux-headers \
supervisor \
&& rm -rf /tmp/* /var/cache/apk/*
RUN docker-php-ext-configure zip && docker-php-ext-install zip RUN docker-php-ext-configure zip && docker-php-ext-install zip
RUN docker-php-ext-install gd pdo pdo_mysql zip RUN docker-php-ext-install gd pdo pdo_mysql zip
@@ -59,11 +63,12 @@ RUN docker-php-ext-install gd pdo pdo_mysql zip
# Get latest Composer # Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www ARG wd=/var/www
WORKDIR ${wd}
COPY . . COPY . .
COPY --from=build-vue /usr/app/public/build ./public/build COPY --from=build-vue /usr/app/public/build ./public/build
RUN mv .env.docker .env RUN mv ${APP_ENV_FILE} .env
# DUSK # DUSK
# RUN php artisan dusk:install && php artisan dusk:chrome-driver && mv ./undetectedChromedriver/chromedriver-linux ./vendor/laravel/dusk/bin/chromedriver-linux # RUN php artisan dusk:install && php artisan dusk:chrome-driver && mv ./undetectedChromedriver/chromedriver-linux ./vendor/laravel/dusk/bin/chromedriver-linux
@@ -72,16 +77,17 @@ RUN mv .env.docker .env
RUN composer install --no-interaction --prefer-dist RUN composer install --no-interaction --prefer-dist
# Supervisor
COPY ./docker/datbrowserQueueWorker.conf /etc/supervisor/conf.d/datbrowserQueueWorker.conf
COPY ./docker/supervisord.conf /etc/supervisord.conf
# RUN supervisorctl reread && supervisorctl update
# CRON # CRON
RUN echo "* * * * * cd /usr/app && php artisan schedule:run >> /dev/null 2>&1" > /etc/crontabs/root RUN echo "* * * * * cd ${wd} && php artisan schedule:run >> /dev/null 2>&1" > /etc/crontabs/root
# Link the storage directory to the public directory. # Link the storage directory to the public directory.
RUN php artisan storage:link RUN php artisan storage:link
# Laravel optimization commands
# RUN php artisan cache:clear
RUN php artisan config:cache && php artisan route:cache
RUN chmod +x ./dockerEntryPoint.sh RUN chmod +x ./dockerEntryPoint.sh
EXPOSE 80 EXPOSE 80
+15 -6
View File
@@ -2,8 +2,11 @@
namespace App\Browser; namespace App\Browser;
use App\Models\Job;
use App\Models\JobArtifact; use App\Models\JobArtifact;
use App\Models\JobRun; use App\Models\JobRun;
use App\Notification\Notifications\JobErrorNotification;
use App\Notification\Providers\AllNotification;
use Closure; use Closure;
use Exception; use Exception;
use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Chrome\ChromeOptions;
@@ -28,6 +31,8 @@ abstract class BrowserJob implements ShouldQueue
public int $jobId; public int $jobId;
public $timeout = 500;
public function __construct(int $jobId) public function __construct(int $jobId)
{ {
$this->jobId = $jobId; $this->jobId = $jobId;
@@ -53,7 +58,8 @@ abstract class BrowserJob implements ShouldQueue
// throw $e; // throw $e;
} }
catch (Throwable $e) { catch (Throwable $e) {
$browser->screenshot("failure-{$this->jobId}"); $browser->screenshot(JobErrorScreenshot::getFileName($this->jobId));
AllNotification::send(new JobErrorNotification($this->jobId, $e->getMessage()));
dump($e); dump($e);
throw $e; throw $e;
} finally { } finally {
@@ -175,8 +181,7 @@ abstract class BrowserJob implements ShouldQueue
*/ */
protected function hasHeadlessDisabled(): bool protected function hasHeadlessDisabled(): bool
{ {
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) || return config('dusk.headlessDisabled', false);
isset($_ENV['DUSK_HEADLESS_DISABLED']);
} }
/** /**
@@ -184,8 +189,7 @@ abstract class BrowserJob implements ShouldQueue
*/ */
protected function shouldStartMaximized(): bool protected function shouldStartMaximized(): bool
{ {
return isset($_SERVER['DUSK_START_MAXIMIZED']) || return config('dusk.shouldStartMaximized', false);
isset($_ENV['DUSK_START_MAXIMIZED']);
} }
/** /**
@@ -214,8 +218,14 @@ abstract class BrowserJob implements ShouldQueue
*/ */
public function handle(): void public function handle(): void
{ {
if (Job::find($this->jobId)->is_active) {
$this->execute(); $this->execute();
} }
}
public function reschedule($minutes) {
$this::dispatch()->delay(now()->addMinutes($minutes));
}
// === BROWSER MACROS === // === BROWSER MACROS ===
@@ -268,7 +278,6 @@ abstract class BrowserJob implements ShouldQueue
protected function findElementContainingElementWithText(Browser $browser, string $elementXpath, string $text, bool $ignoreCase = true): RemoteWebElement|null { protected function findElementContainingElementWithText(Browser $browser, string $elementXpath, string $text, bool $ignoreCase = true): RemoteWebElement|null {
try { try {
if ($ignoreCase) { if ($ignoreCase) {
dump("{$elementXpath}[.//*[{$this->xpathContainsIgnoreCase($text)}]]");
return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[{$this->xpathContainsIgnoreCase($text)}]]")); return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[{$this->xpathContainsIgnoreCase($text)}]]"));
} else { } else {
return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[contains(text(), \"{$text}\")]]")); return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[contains(text(), \"{$text}\")]]"));
@@ -37,7 +37,11 @@ class MainNav extends BaseComponent
} }
public function goToHome(Browser $browser) { public function goToHome(Browser $browser) {
try {
$browser->scrollIntoView('@logo'); $browser->scrollIntoView('@logo');
$browser->click('@logo'); $browser->click('@logo');
} catch (\Exception $e) {
$browser->visit('https://hellcase.com/');
}
} }
} }
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Browser;
use Laravel\Dusk\Browser;
use function rtrim;
class JobDebugScreenshot {
public const IMG_FILE_NAME = "debug-";
public static function getFileName(int $jobId): string {
return static::IMG_FILE_NAME . $jobId . ".png";
}
public static function getImgFileAbsolutePath(int $jobId): string {
return rtrim(Browser::$storeScreenshotsAt, '/') . "/" . static::getFileName($jobId);
}
public static function getImgFileProjectPath(int $jobId): string {
return app_path("Browser/screenshots/" . static::getFileName($jobId));
}
public static function getImgFileExternalPath(int $jobId): string {
return "screenshots/" . static::getFileName($jobId);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Browser;
use Laravel\Dusk\Browser;
use function rtrim;
class JobErrorScreenshot {
public const IMG_FILE_NAME = "failure-";
public static function getFileName(int $jobId): string {
return static::IMG_FILE_NAME . $jobId . ".png";
}
public static function getImgFileAbsolutePath(int $jobId): string {
return rtrim(Browser::$storeScreenshotsAt, '/') . "/" . static::getFileName($jobId);
}
public static function getImgFileProjectPath(int $jobId): string {
return app_path("Browser/screenshots/" . static::getFileName($jobId));
}
public static function getImgFileExternalPath(int $jobId): string {
return "screenshots/" . static::getFileName($jobId);
}
}
+105 -23
View File
@@ -4,33 +4,38 @@ namespace App\Browser\Jobs\Hellcase;
use App\Browser\BrowserJob; use App\Browser\BrowserJob;
use App\Browser\Components\Hellcase\MainNav; use App\Browser\Components\Hellcase\MainNav;
use App\Models\JobArtifact; use App\Browser\JobDebugScreenshot;
use App\Browser\JobArtifacts\JobRunArtifact;
use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode; use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode;
use App\Models\JobArtifact;
use App\Models\JobRun; use App\Models\JobRun;
use App\Notification\NotificationBody\Hellcase\HellcaseNotificationDailyFreeBody; use App\Notification\NotificationBody\Hellcase\HellcaseNotificationDailyFreeBody;
use App\Notification\NotificationBody\Hellcase\HellcaseNotificationLoginBody; use App\Notification\NotificationBody\Hellcase\HellcaseNotificationLoginBody;
use App\Notification\Notifications\Hellcase\HellcaseNotificationDailyFree; use App\Notification\Notifications\Hellcase\HellcaseNotificationDailyFree;
use App\Notification\Notifications\Hellcase\HellcaseNotificationLogin; use App\Notification\Notifications\Hellcase\HellcaseNotificationLogin;
use App\Notification\Notifications\JobDebugNotification;
use App\Notification\Notifications\JobErrorNotification;
use App\Notification\Providers\AllNotification; use App\Notification\Providers\AllNotification;
use Dom\XPath;
use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverBy;
use Illuminate\Support\Facades\Schedule; use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Support\Facades\Log;
use Laravel\Dusk\Browser; use Laravel\Dusk\Browser;
class HellcaseJob extends BrowserJob class HellcaseJob extends BrowserJob implements ShouldBeUniqueUntilProcessing
{ {
private const STEAM_LOGIN_THRESHOLD = 5 * 60; // 5 minutes private const STEAM_LOGIN_THRESHOLD = 5 * 60; // 5 minutes
private const APPROXIMATIVE_RUNNING_MINUTES = 2;
private JobRun $jobRun; protected JobRun $jobRun;
public function __construct() public function __construct($jobId = 2)
{ {
parent::__construct(2); Log::info("Constructing HellcaseJob");
parent::__construct($jobId);
} }
public function run(Browser $browser): ?JobRun public function run(Browser $browser): ?JobRun
{ {
Log::info("Running HellcaseJob");
$this->jobRun = new JobRun([ $this->jobRun = new JobRun([
"job_id" => $this->jobId, "job_id" => $this->jobId,
"success" => false, "success" => false,
@@ -38,16 +43,24 @@ class HellcaseJob extends BrowserJob
$this->jobRun->save(); $this->jobRun->save();
$browser->visit('https://hellcase.com'); $browser->visit('https://hellcase.com');
$browser->waitForText("Store", 30, true); $browser->waitForText("CASES", 30, true);
$this->removePopups($browser); $this->removePopups($browser);
sleep(5); sleep(5);
$this->signin($browser); $this->signin($browser);
try {
$this->joinFreeGiveaways($browser); $this->joinFreeGiveaways($browser);
} catch (\Exception $e) {
$this->jobRun->success = false;
$this->jobRun->save();
AllNotification::send(new JobErrorNotification($this->jobId, "Erreur lors de la participation aux concours gratuits : " . $e->getMessage()));
}
$this->getDailyFree($browser); $this->getDailyFree($browser);
$this->jobRun->success = true; $this->jobRun->success = true;
$this->jobRun->save(); $this->jobRun->save();
Log::info("HellcaseJob run ended");
return $this->jobRun; return $this->jobRun;
} }
@@ -58,7 +71,7 @@ class HellcaseJob extends BrowserJob
{ {
try { try {
$browser->visit('https://hellcase.com'); $browser->visit('https://hellcase.com');
$browser->waitForText("REFILL", 30, true); $browser->waitForText("CASES", 30, true);
$this->removePopups($browser); $this->removePopups($browser);
sleep(2); sleep(2);
$this->signin($browser); $this->signin($browser);
@@ -76,7 +89,7 @@ class HellcaseJob extends BrowserJob
} }
} }
private function signin(Browser $browser) protected function signin(Browser $browser)
{ {
try { try {
$browser->clickAtXPath('//button[.//span[text() = "Sign in"]]'); $browser->clickAtXPath('//button[.//span[text() = "Sign in"]]');
@@ -84,14 +97,23 @@ class HellcaseJob extends BrowserJob
return; return;
} }
sleep(5);
$browser->waitForText("Sign in with Steam", 30, true);
sleep(3); sleep(3);
$browser->clickAtXPath('//button[.//span[contains(text(), "Sign in through Steam")]]'); $browser->driver->findElement(WebDriverBy::xpath('//button[contains(@class,"_base_zvftr_1 _accent-1_zvftr_105 _m_zvftr_52 _full_zvftr_94 _primary_zvftr_100")]'))->click();
sleep(5); sleep(5);
// QR CODE SCANNING // QR CODE SCANNING
try { try {
$browser->waitForTextIn("div", "Or sign in with QR", 30, true); $browser->waitForTextIn("div", "Or sign in with QR", 30, true);
sleep(10);
try {
$qrCode = $browser->driver->findElement(WebDriverBy::xpath('//div[./*[contains(text(), "Or sign in with QR")]]')); $qrCode = $browser->driver->findElement(WebDriverBy::xpath('//div[./*[contains(text(), "Or sign in with QR")]]'));
} catch (\Exception $e) {
$browser->screenshot(JobDebugScreenshot::getFileName($this->jobId));
AllNotification::send(new JobDebugNotification($this->jobId, "Le QR code de la page de connexion de Steam n'a pas été trouvé"));
throw $e;
}
// Wait to be redirected to the Steam login page, while waiting take a new screenshot every 30 seconds // Wait to be redirected to the Steam login page, while waiting take a new screenshot every 30 seconds
$isBackOnHellcase = false; $isBackOnHellcase = false;
@@ -116,17 +138,19 @@ class HellcaseJob extends BrowserJob
} }
} catch (\Exception $e) { } catch (\Exception $e) {
// If the QR code is not found, we are not on the QR code page // If the QR code is not found, we are not on the QR code page
Log::debug("Exception because qrcode not found : " . $e);
$isBackOnHellcase = true; $isBackOnHellcase = true;
} catch (\Throwable $e) { } catch (\Throwable $e) {
// If the QR code is not found, we are not on the QR code page // If the QR code is not found, we are not on the QR code page
Log::debug("Exception because qrcode not found : " . $e);
$isBackOnHellcase = true; $isBackOnHellcase = true;
} }
if ($isBackOnHellcase) { if ($isBackOnHellcase) {
// Click a button tjat says "sign in" // Click a button that says "sign in"
$browser->waitForText("By signing into steam.loginhell.com through Steam", 30, true); $browser->waitForText("By signing into steam.loginhell.com through Steam", 30, true);
$browser->clickAtXPath('//input[@id = "imageLogin"]'); $browser->clickAtXPath('//input[@id = "imageLogin"]');
sleep(30); sleep(20);
} }
} }
@@ -135,6 +159,8 @@ class HellcaseJob extends BrowserJob
try { try {
$buttons = $browser->driver->findElements(WebDriverBy::xpath('//a[text() = "Join for free"]')); $buttons = $browser->driver->findElements(WebDriverBy::xpath('//a[text() = "Join for free"]'));
} catch (\Exception $e) { } catch (\Exception $e) {
$browser->screenshot(JobDebugScreenshot::getFileName($this->jobId));
AllNotification::send(new JobDebugNotification($this->jobId, "No join for free buttons found"));
return; return;
} }
@@ -144,9 +170,31 @@ class HellcaseJob extends BrowserJob
"content" => "" "content" => ""
])); ]));
} }
try {
$nextSlideButton = $browser->driver->findElement(WebDriverBy::xpath('//button[@class="_button_1ygbm_7 _next_1ygbm_24"]'));
} catch (\Exception $e) {
$browser->screenshot(JobDebugScreenshot::getFileName($this->jobId));
AllNotification::send(new JobDebugNotification($this->jobId, "No next slide button found"));
return;
}
foreach ($buttons as $button) { foreach ($buttons as $button) {
// Click the next slide button if the button is not clickable
$clickedFailsCounter = 0;
while ($clickedFailsCounter < 7 && $clickedFailsCounter >= 0) {
try {
$button->click(); $button->click();
sleep(5); } catch (\Exception $e) {
$clickedFailsCounter++;
try {
$nextSlideButton->click();
} catch (\Exception $_) {}
sleep(3);
continue;
}
$clickedFailsCounter = -1;
}
sleep(5); // Wait a bit for loading
$this->joinGiveaway($browser); $this->joinGiveaway($browser);
$browser->within(new MainNav, function (Browser $browser) { $browser->within(new MainNav, function (Browser $browser) {
$browser->goToHome(); $browser->goToHome();
@@ -158,6 +206,21 @@ class HellcaseJob extends BrowserJob
{ {
$joinButton = $browser->driver->findElement(WebDriverBy::xpath('//button[span[contains(text(), "Join for free")]]')); $joinButton = $browser->driver->findElement(WebDriverBy::xpath('//button[span[contains(text(), "Join for free")]]'));
$joinButton->click(); $joinButton->click();
sleep(3);
// JobRun
// Get the elements text containing class starting with giveaway-entity-prize__
$prizeElement = $browser->driver->findElements(WebDriverBy::xpath('//div[starts-with(@class, "giveaway-entity-prize__")]'));
// Join their text
$prize = "";
foreach ($prizeElement as $element) {
$prize .= $element->getText() . "\n";
}
$this->jobRun->addArtifact(new JobArtifact([
"name" => "Rejoint le concours",
"content" => "Pour tenter de gagner le prix : \n" . $prize
]));
} }
private function getDailyFree(Browser $browser) private function getDailyFree(Browser $browser)
@@ -174,25 +237,40 @@ class HellcaseJob extends BrowserJob
$availibleInButton = $browser->driver->findElement(WebDriverBy::xpath('//button[contains(@class, "daily-free-banner__button")]')); $availibleInButton = $browser->driver->findElement(WebDriverBy::xpath('//button[contains(@class, "daily-free-banner__button")]'));
if ($availibleInButton->getAttribute("disabled") == "true") { if ($availibleInButton->getAttribute("disabled") == "true") {
$hours = $availibleInButton->getText(); $hours = $availibleInButton->getText();
// If the text is like "in 26 sec." we need to put one minute
if (str_contains(strtolower($hours), "seconds")) {
$browser->screenshot(JobDebugScreenshot::getFileName($this->jobId));
AllNotification::send(new JobDebugNotification($this->jobId, "I hate niggers"));
// $this->reschedule(1);
sleep(60);
return $this->getDailyFree($browser);
}
$hours = explode(" ", $hours); $hours = explode(" ", $hours);
$minutes = $hours[4]; $minutes = $hours[4];
$hours = $hours[2]; $hours = $hours[2];
// $this->reschedule($hours);
$this->jobRun->addArtifact(new JobArtifact([ $this->jobRun->addArtifact(new JobArtifact([
"name" => "Cadeau gratuit pas encore disponible", "name" => "Cadeau gratuit pas encore disponible",
"content" => "Le cadeau gratuit journalier sera disponible dans {$hours} heures et {$minutes} minutes.\nDatboi se fera un plaisir d'aller le chercher pour vous." "content" => "Le cadeau gratuit journalier sera disponible dans {$hours} heures et {$minutes} minutes."
])); ]));
$this->reschedule($hours * 60 + $minutes + 1);
return; return;
} }
// Click the dailyfree button // Click the dailyfree button
$availibleInButton->click(); $availibleInButton->click();
sleep(15);
$browser->waitForText("Your drop", 30, true);
$lootElement = $browser->driver->findElement(WebDriverBy::xpath('//div[contains(@class, "daily-free-win-bonus")]')); $lootElement = $browser->driver->findElement(WebDriverBy::xpath('//div[contains(@class, "daily-free-win-bonus")]'));
$lootElement->takeElementScreenshot(HellcaseDailyFreeScreenshot::getImgFileAbsolutePath()); $lootElement->takeElementScreenshot(HellcaseDailyFreeScreenshot::getImgFileAbsolutePath());
$this->reschedule((24 * 60) - ($this::APPROXIMATIVE_RUNNING_MINUTES / 2)); // Reschedule in 24hr -1min
AllNotification::send( AllNotification::send(
new HellcaseNotificationDailyFree($this->jobId, new HellcaseNotificationDailyFreeBody()) new HellcaseNotificationDailyFree($this->jobId, new HellcaseNotificationDailyFreeBody())
); );
@@ -204,7 +282,8 @@ class HellcaseJob extends BrowserJob
* @throws \Exception * @throws \Exception
* @return void * @return void
*/ */
private function fillDailyFreeConditions(Browser $browser) { private function fillDailyFreeConditions(Browser $browser)
{
// 1. See what conditions we need to fullfill // 1. See what conditions we need to fullfill
$conditions = []; $conditions = [];
$conditionsDivs = $browser->driver->findElements(WebDriverBy::xpath('//*[@class = "daily-free-requirement__heading-left"]')); $conditionsDivs = $browser->driver->findElements(WebDriverBy::xpath('//*[@class = "daily-free-requirement__heading-left"]'));
@@ -235,13 +314,14 @@ class HellcaseJob extends BrowserJob
* @param \Laravel\Dusk\Browser $browser * @param \Laravel\Dusk\Browser $browser
* @return void * @return void
*/ */
private function changeSteamProfilePicture(Browser $browser) { private function changeSteamProfilePicture(Browser $browser)
{
// Get all of the availible image link // Get all of the availible image link
$images = $browser->driver->findElements(WebDriverBy::xpath('//a[@class = "daily-free-user-requirement-avatar-item"]')); $images = $browser->driver->findElements(WebDriverBy::xpath('//a[@class = "daily-free-user-requirement-avatar-item"]'));
// Download the image from the second link in a special folder // Download the image from the second link in a special folder
$imageLink = $images[1]->getAttribute("href"); $imageLink = $images[1]->getAttribute("href");
// Download the image in app/Browser/downloads/ // Download the image in app/Browser/downloads/
$imagePath = base_path("app/Browser/downloads/Hellcase/pp.jpg"); $imagePath = base_path("app/Browser/downloads/Hellcase/pp.png");
file_put_contents($imagePath, file_get_contents($imageLink)); file_put_contents($imagePath, file_get_contents($imageLink));
$this->goToSteamProfileSettings($browser); $this->goToSteamProfileSettings($browser);
@@ -274,7 +354,8 @@ class HellcaseJob extends BrowserJob
} }
} }
private function changeSteamProfileToPublic(Browser $browser) { private function changeSteamProfileToPublic(Browser $browser)
{
$this->goToSteamProfileSettings($browser); $this->goToSteamProfileSettings($browser);
// Wait for and click "Privacy Settings" // Wait for and click "Privacy Settings"
@@ -298,7 +379,8 @@ class HellcaseJob extends BrowserJob
* @param \Laravel\Dusk\Browser $browser * @param \Laravel\Dusk\Browser $browser
* @return void * @return void
*/ */
private function goToSteamProfileSettings(Browser $browser) { private function goToSteamProfileSettings(Browser $browser)
{
// Get the link that has text "Steam profile" // Get the link that has text "Steam profile"
$steamProfileLink = $browser->driver->findElement(WebDriverBy::xpath('//a[contains(text(), "Steam profile")]')); $steamProfileLink = $browser->driver->findElement(WebDriverBy::xpath('//a[contains(text(), "Steam profile")]'));
$browser->visit($steamProfileLink->getAttribute("href")); $browser->visit($steamProfileLink->getAttribute("href"));
@@ -308,7 +390,7 @@ class HellcaseJob extends BrowserJob
$browser->clickAtXPath('//*[contains(text(), "Edit Profile")]'); $browser->clickAtXPath('//*[contains(text(), "Edit Profile")]');
} }
private function removePopups(Browser $browser) protected function removePopups(Browser $browser)
{ {
// $browser->script('document.querySelector("div.app-modal")[0].remove();'); // $browser->script('document.querySelector("div.app-modal")[0].remove();');
// $browser->driver->executeScript('document.querySelector("div.app-modal")[0].remove();'); // $browser->driver->executeScript('document.querySelector("div.app-modal")[0].remove();');
@@ -0,0 +1,145 @@
<?php
namespace App\Browser\Jobs\HellcaseBattles;
use App\Browser\Jobs\Hellcase\HellcaseJob;
use App\Models\HellcaseBattle;
use App\Models\Job;
use App\Models\JobRun;
use App\Notification\Notifications\JobDebugNotification;
use App\Notification\Providers\AllNotification;
use Exception;
use Facebook\WebDriver\WebDriver;
use Facebook\WebDriver\WebDriverBy;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Laravel\Dusk\Browser;
class HellcaseBattlesJob extends HellcaseJob implements ShouldBeUniqueUntilProcessing
{
private Collection $jobInfos;
private array $battlesToAdd = [];
public function __construct()
{
Log::info("Constructing HellcaseBattlesJob");
parent::__construct(3);
}
public function run(Browser $browser): ?JobRun
{
$this->jobInfos = Job::find($this->jobId)->jobInfosTable();
Log::info("Running HellcaseBattlesJob");
$this->jobRun = new JobRun([
"job_id" => $this->jobId,
"success" => false,
]);
$this->jobRun->save();
$browser->visit('https://hellcase.com');
$browser->waitForText("CASES", 30, true);
$this->removePopups($browser);
sleep(5);
$this->signin($browser);
$this->saveInterestingBattles($browser);
$this->sendFinishedBattles($browser);
$this->createNewBattles();
$this->jobRun->success = true;
$this->jobRun->save();
Log::info("HellcaseBattlesJob run ended");
return $this->jobRun;
}
/**
* Save current cases battles to database for later processing
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
private function saveInterestingBattles(Browser $browser)
{
$battleIndex = 0; // Index of the battle to get info from
$running = true;
while ($running) {
$browser->visit('https://hellcase.com/casebattle');
$browser->waitForText("CASES", 30, true);
AllNotification::send(new JobDebugNotification($this->jobId, "I hate niggers"));
// Sort by price
try {
$sortByPriceDiv = $browser->driver->findElement(WebDriverBy::xpath("//*[span[contains(text(), 'Value')]]"));
$sortByPriceDiv->click();
} catch (Exception $e) {
AllNotification::send(new JobDebugNotification($this->jobId, "Failed to sort by price"));
return;
}
sleep(5);
$battles = $browser->driver->findElements(WebDriverBy::xpath("//*[contains(@class, 'casebattle-table__item')]"));
$battle = $battles[$battleIndex];
$battleIndex++;
$browser->scrollIntoView(".casebattle-table__item:nth-child(" . max($battleIndex -1, 1) . ")");
sleep(2);
$battleValue = floatval(
explode(
"\n",
$battle->findElement(WebDriverBy::xpath("./div/div[contains(@class, 'core-price')]"))->getDomProperty("innerText")
)[1]
);
if ($battleValue < floatval($this->jobInfos->get("hellcase_battles_minimum_value"))) {
$running = false;
break;
}
$battleLinkButton = $battle->findElement(WebDriverBy::xpath('./div//button[text() = "watch"]'));
$battleLinkButton->sendKeys("\n");
sleep(3);
$battleLink = $browser->driver->getCurrentURL();
$this->battlesToAdd[$battleLink] = $battleValue;
}
}
private function sendFinishedBattles(Browser $browser) {
// foreach battle that we didn"t already planned to add with $this->battlesToAdd
foreach (HellcaseBattle::all() as $battle) {
dump($battle);
if (!array_key_exists($battle->getUrl(), $this->battlesToAdd)) {
dump("finished");
$browser->visit($battle->getUrl());
try {
$browser->waitForText("Started at");
// Send the battle
$this->sendBattle($browser, $battle);
} catch (Exception $e) { // Battle is not finished or error (like battle cancelled)
}
$battle->delete();
}
}
}
private function sendBattle(Browser $browser, HellcaseBattle $battle) {
AllNotification::send(new JobDebugNotification($this->jobId, "Battle sent" . $battle->getUrl()));
}
private function createNewBattles() {
foreach ($this->battlesToAdd as $battleLink => $battleValue) {
$battleLink = explode("/", $battleLink);
HellcaseBattle::firstOrCreate([
"battle_id" => $battleLink[count($battleLink) - 1],
"value" => $battleValue,
]);
}
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ class JobController extends Controller
public function show($jobId, Request $request) public function show($jobId, Request $request)
{ {
return Inertia::render('Job', [ return Inertia::render('Job', [
'job' => Job::where('id', $jobId)->with('jobInfos')->first(), 'job' => Job::where('id', $jobId)->with('jobInfos', 'jobRuns')->first(),
'error' => $request->input('error'), 'error' => $request->input('error'),
]); ]);
} }
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Jobs;
use App\Models\Job;
use App\Models\JobRun;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class PruneOldJobRuns implements ShouldQueue
{
use Queueable;
/**
* Create a new job instance.
*/
public function __construct()
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
// For each job, keep only the last N runs
foreach (Job::all() as $job) {
$job->jobRuns()
->orderByDesc('id')
->skip(config('jobs.pruneOldJobRuns.max_runs_per_job'))
->delete();
}
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class HellcaseBattle extends Model
{
protected $fillable = [
"battle_id",
"value",
];
public function getUrl() {
return "https://hellcase.com/casebattle/{$this->battle_id}";
}
}
+10
View File
@@ -21,6 +21,16 @@ class Job extends Model
return $this->hasMany(JobInfo::class)->with("jobInfoType")->orderBy("created_at"); return $this->hasMany(JobInfo::class)->with("jobInfoType")->orderBy("created_at");
} }
/**
* Get an associative collection of the job infos with their values
* @return \Illuminate\Database\Eloquent\Collection<string, string>>
*/
public function jobInfosTable() {
return $this->jobInfos->mapWithKeys(function ($jobInfo) {
return [$jobInfo->key => $jobInfo->value];
});
}
public function jobRuns() public function jobRuns()
{ {
return $this->hasMany(JobRun::class)->orderBy("created_at"); return $this->hasMany(JobRun::class)->orderBy("created_at");
+8 -1
View File
@@ -4,6 +4,7 @@ namespace App\Notification;
use App\Models\Job; use App\Models\Job;
use App\Notification\Stringifiable\StringifiableSimpleText; use App\Notification\Stringifiable\StringifiableSimpleText;
use function PHPUnit\Framework\isNull;
abstract class Notification { abstract class Notification {
@@ -12,12 +13,18 @@ abstract class Notification {
public bool $isError; public bool $isError;
public function __construct(int $jobId, NotificationBody $body, bool $isError = false) { public function __construct(int $jobId, NotificationBody $body = null, bool $isError = false) {
$this->job = Job::find($jobId); $this->job = Job::find($jobId);
if ($body !== null) {
$this->body = $body; $this->body = $body;
}
$this->isError = $isError; $this->isError = $isError;
} }
public function setBody(NotificationBody $body) {
$this->body = $body;
}
public function getTitle(): Stringifiable { public function getTitle(): Stringifiable {
return new StringifiableSimpleText($this->job->name); return new StringifiableSimpleText($this->job->name);
} }
@@ -0,0 +1,51 @@
<?php
namespace App\Notification\NotificationBody;
use App\Models\Job;
use App\Notification\NotificationBody;
use App\Notification\Stringifiable;
class JobDebugNotificationBody extends NotificationBody {
private Job $job;
private string $body;
private ?string $error;
private bool $hasScreenshot;
public function __construct(Job $job, string $body, string $error = null, bool $hasScreenshot = false) {
$this->job = $job;
$this->body = $body;
$this->error = $error;
$this->hasScreenshot = $hasScreenshot;
}
private function constructString(bool $inMarkdown = false) {
$mdBody = "";
if ($this->body !== null) {
$mdBody .= $this->body;
}
if ($this->error !== null) {
$errorWrapper = $inMarkdown ? "```" : "";
$mdBody .= " :\n" . $errorWrapper . $this->error . $errorWrapper;
}
if ($inMarkdown && $this->hasScreenshot) {
$mdBody .= "\nScreenshot : ";
}
return $mdBody;
}
/**
* @inheritDoc
*/
public function toMarkdownString(): string {
return $this->constructString(true);
}
/**
* @inheritDoc
*/
public function toString(): string {
return $this->constructString();
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Notification\NotificationBody;
use App\Models\Job;
use App\Notification\NotificationBody;
use App\Notification\Stringifiable;
class JobErrorNotificationBody extends NotificationBody {
private Job $job;
private string $error;
public function __construct(Job $job, $error) {
$this->job = $job;
$this->error = $error;
}
/**
* @inheritDoc
*/
public function toMarkdownString(): string {
return "Le job \"{$this->job->name}\" a échoué avec l'erreur :\n ```" . $this->error . "``` \nScreenshot : ";
}
/**
* @inheritDoc
*/
public function toString(): string {
return "Le job \"{$this->job->name}\" a échoué avec l'erreur :\n " . $this->error;
}
}
@@ -3,12 +3,9 @@
namespace App\Notification\Notifications\Hellcase; namespace App\Notification\Notifications\Hellcase;
use App\Browser\Jobs\Hellcase\HellcaseDailyFreeScreenshot; use App\Browser\Jobs\Hellcase\HellcaseDailyFreeScreenshot;
use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode;
use App\Notification\Notification; use App\Notification\Notification;
use App\Notification\Notifications\NotificationLogin;
use Laravel\Dusk\Browser;
class HellcaseNotificationDailyFree extends NotificationLogin { class HellcaseNotificationDailyFree extends Notification {
public function __construct(int $jobId, \App\Notification\NotificationBody $body) { public function __construct(int $jobId, \App\Notification\NotificationBody $body) {
parent::__construct($jobId, $body); parent::__construct($jobId, $body);
@@ -0,0 +1,44 @@
<?php
namespace App\Notification\Notifications;
use App\Browser\JobDebugScreenshot;
use App\Notification\Notification;
use App\Notification\NotificationBody\JobDebugNotificationBody;
use App\Notification\Stringifiable;
use App\Notification\Stringifiable\StringifiableSimpleText;
use Illuminate\Support\Facades\Log;
class JobDebugNotification extends Notification {
private string|null $title;
private string|null $screenShotProjectPath;
public function __construct(int $jobId, string $body, string $title = null, string $error = null, ?string $screenshotProjectPath = "", bool $isError = false) {
parent::__construct($jobId, isError:$isError);
$this->title = $title;
if ($screenshotProjectPath === "") {
$screenshotProjectPath = JobDebugScreenshot::getImgFileProjectPath($jobId);
}
$this->screenShotProjectPath = $screenshotProjectPath;
$this->setBody(new JobDebugNotificationBody($this->job, $body, $error, $this->screenShotProjectPath != null));
}
public function getTitle(): Stringifiable {
return new StringifiableSimpleText($this->title ?? "DEBUG Job {$this->job->name}");
}
/**
* @inheritDoc
*/
public function getImageProjectPath(): string|null {
return $this->screenShotProjectPath;
}
/**
* @inheritDoc
*/
public function getLinkURL(): string|null {
return route('jobs.show', ['job' => $this->job->id]);
}
}
@@ -0,0 +1,37 @@
<?php
namespace App\Notification\Notifications;
use App\Browser\JobErrorScreenshot;
use App\Models\Job;
use App\Notification\Notification;
use App\Notification\NotificationBody\JobErrorNotificationBody;
use App\Notification\Stringifiable;
use App\Notification\Stringifiable\StringifiableSimpleText;
use Illuminate\Support\Facades\Log;
class JobErrorNotification extends Notification {
public function __construct(int $jobId, string $error) {
parent::__construct($jobId, isError:true);
$this->setBody(new JobErrorNotificationBody($this->job, $error));
}
public function getTitle(): Stringifiable {
return new StringifiableSimpleText("Le job {$this->job->name} a échoué");
}
/**
* @inheritDoc
*/
public function getImageProjectPath(): string|null {
return JobErrorScreenshot::getImgFileProjectPath($this->job->id);
}
/**
* @inheritDoc
*/
public function getLinkURL(): string|null {
return route('jobs.show', ['job' => $this->job->id]);
}
}
@@ -3,9 +3,6 @@
namespace App\Notification\Providers; namespace App\Notification\Providers;
use App\Notification\NotificationProvider; use App\Notification\NotificationProvider;
use App\Notification\INotificationProvider;
use App\Models\JobInfo;
use Illuminate\Support\Facades\Cache;
class AllNotification extends NotificationProvider { class AllNotification extends NotificationProvider {
private const NOTIFICATIONS_PROVIDERS = [ private const NOTIFICATIONS_PROVIDERS = [
@@ -3,7 +3,6 @@
namespace App\Notification\Providers; namespace App\Notification\Providers;
use App\Notification\NotificationProvider; use App\Notification\NotificationProvider;
use App\Notification\INotificationProvider;
use App\Models\JobInfo; use App\Models\JobInfo;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
+6 -1
View File
@@ -3,6 +3,7 @@
namespace App\Providers; namespace App\Providers;
use App\Browser\BrowserJob; use App\Browser\BrowserJob;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Facades\Vite; use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@@ -19,8 +20,12 @@ class AppServiceProvider extends ServiceProvider
/** /**
* Bootstrap any application services. * Bootstrap any application services.
*/ */
public function boot(): void public function boot(UrlGenerator $url): void
{ {
Vite::prefetch(concurrency: 3); Vite::prefetch(concurrency: 3);
if (str_starts_with(config('app.url'), "https://")) {
$url->forceScheme('https');
}
} }
} }
@@ -0,0 +1,67 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
Telescope::night();
$this->hideSensitiveRequestDetails();
$isLocal = $this->app->environment('local');
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
return true;
/* $isLocal ||
$entry->isReportableException() ||
$entry->isFailedRequest() ||
$entry->isFailedJob() ||
$entry->isScheduledTask() ||
$entry->isCache() ||
$entry->isDump() ||
$entry->isQuery() ||
$entry->isSlowQuery() ||
$entry->hasMonitoredTag(); */
});
}
/**
* Prevent sensitive request details from being logged by Telescope.
*/
protected function hideSensitiveRequestDetails(): void
{
if ($this->app->environment('local')) {
return;
}
Telescope::hideRequestParameters(['_token']);
Telescope::hideRequestHeaders([
'cookie',
'x-csrf-token',
'x-xsrf-token',
]);
}
/**
* Register the Telescope gate.
*
* This gate determines who can access Telescope in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewTelescope', function ($user) {
return true;
});
}
}
+1
View File
@@ -9,6 +9,7 @@ return Application::configure(basePath: dirname(__DIR__))
web: __DIR__.'/../routes/web.php', web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php', api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php', commands: __DIR__.'/../routes/console.php',
channels: __DIR__.'/../routes/channels.php',
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware) { ->withMiddleware(function (Middleware $middleware) {
+1
View File
@@ -3,4 +3,5 @@
return [ return [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
App\Providers\BrowserJobsServiceProvider::class, App\Providers\BrowserJobsServiceProvider::class,
App\Providers\TelescopeServiceProvider::class,
]; ];
+6 -4
View File
@@ -2,7 +2,6 @@
"$schema": "https://shadcn-vue.com/schema.json", "$schema": "https://shadcn-vue.com/schema.json",
"style": "default", "style": "default",
"typescript": true, "typescript": true,
"tsConfigPath": "./tsconfig.json",
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "tailwind.config.js",
"css": "resources/css/app.css", "css": "resources/css/app.css",
@@ -10,9 +9,12 @@
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"framework": "laravel",
"aliases": { "aliases": {
"components": "@/Components", "components": "@/Components",
"utils": "@/lib/utils" "composables": "@/composables",
} "utils": "@/lib/utils",
"ui": "@/Components/ui",
"lib": "@/lib"
},
"iconLibrary": "lucide"
} }
-2
View File
@@ -4,8 +4,6 @@ services:
restart: unless-stopped restart: unless-stopped
ports: ports:
- 80:80 - 80:80
environment:
APP_URL: datbrowser.matthiasg.dev
volumes: volumes:
- browserDownloads:/app/Browser/downloads - browserDownloads:/app/Browser/downloads
- browserScreenshots:/app/Browser/screenshots - browserScreenshots:/app/Browser/screenshots
+3 -1
View File
@@ -13,8 +13,10 @@
"erusev/parsedown": "^1.7", "erusev/parsedown": "^1.7",
"inertiajs/inertia-laravel": "^2.0", "inertiajs/inertia-laravel": "^2.0",
"laravel/dusk": "^8.2", "laravel/dusk": "^8.2",
"laravel/framework": "^11.31", "laravel/framework": "^12.0",
"laravel/reverb": "^1.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/telescope": "^5.5",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"tightenco/ziggy": "^2.0" "tightenco/ziggy": "^2.0"
}, },
Generated
+1246 -190
View File
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_CONNECTION', 'reverb'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over WebSockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'reverb' => [
'driver' => 'reverb',
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'),
'options' => [
'host' => env('REVERB_HOST'),
'port' => env('REVERB_PORT', 443),
'scheme' => env('REVERB_SCHEME', 'https'),
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];
+3 -1
View File
@@ -4,5 +4,7 @@ return [
"driver" => [ "driver" => [
"url" => $_ENV['DUSK_DRIVER_URL'] ?? env('DUSK_DRIVER_URL') ?? null "url" => $_ENV['DUSK_DRIVER_URL'] ?? env('DUSK_DRIVER_URL') ?? null
] ],
"shouldStartMaximized" => $_ENV['DUSK_START_MAXIMIZED'] ?? env('DUSK_START_MAXIMIZED') ?? false,
"headlessDisabled" => $_ENV['DUSK_HEADLESS_DISABLED'] ?? env('DUSK_HEADLESS_DISABLED') ?? false
]; ];
+4
View File
@@ -75,6 +75,10 @@ return [
'links' => [ 'links' => [
public_path('storage') => storage_path('app/public'), public_path('storage') => storage_path('app/public'),
public_path('console') => base_path('app/Browser/console'),
public_path('downloads') => base_path('app/Browser/downloads'),
public_path('screenshots') => base_path('app/Browser/screenshots'),
public_path('source') => base_path('app/Browser/source'),
], ],
]; ];
+20
View File
@@ -0,0 +1,20 @@
<?php
use Laravel\Telescope\Http\Middleware\Authorize;
use Laravel\Telescope\Watchers;
return [
/**
* Remove old job runs from the database.
*/
'pruneOldJobRuns' => [
'enabled' => true,
/**
* How many job runs a job can keep before we start pruning old ones.
*/
'max_runs_per_job' => 50,
],
];
+92
View File
@@ -0,0 +1,92 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Reverb Server
|--------------------------------------------------------------------------
|
| This option controls the default server used by Reverb to handle
| incoming messages as well as broadcasting message to all your
| connected clients. At this time only "reverb" is supported.
|
*/
'default' => env('REVERB_SERVER', 'reverb'),
/*
|--------------------------------------------------------------------------
| Reverb Servers
|--------------------------------------------------------------------------
|
| Here you may define details for each of the supported Reverb servers.
| Each server has its own configuration options that are defined in
| the array below. You should ensure all the options are present.
|
*/
'servers' => [
'reverb' => [
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
'port' => env('REVERB_SERVER_PORT', 8080),
'hostname' => env('REVERB_HOST'),
'options' => [
'tls' => [],
],
'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
'scaling' => [
'enabled' => env('REVERB_SCALING_ENABLED', false),
'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
'server' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', '6379'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_DB', '0'),
],
],
'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
],
],
/*
|--------------------------------------------------------------------------
| Reverb Applications
|--------------------------------------------------------------------------
|
| Here you may define how Reverb applications are managed. If you choose
| to use the "config" provider, you may define an array of apps which
| your server will support, including their connection credentials.
|
*/
'apps' => [
'provider' => 'config',
'apps' => [
[
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'),
'options' => [
'host' => env('REVERB_HOST'),
'port' => env('REVERB_PORT', 443),
'scheme' => env('REVERB_SCHEME', 'https'),
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
],
'allowed_origins' => ['*'],
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
],
],
],
];
+206
View File
@@ -0,0 +1,206 @@
<?php
use Laravel\Telescope\Http\Middleware\Authorize;
use Laravel\Telescope\Watchers;
return [
/*
|--------------------------------------------------------------------------
| Telescope Master Switch
|--------------------------------------------------------------------------
|
| This option may be used to disable all Telescope watchers regardless
| of their individual configuration, which simply provides a single
| and convenient way to enable or disable Telescope data storage.
|
*/
'enabled' => env('TELESCOPE_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Telescope Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Telescope will be accessible from. If the
| setting is null, Telescope will reside under the same domain as the
| application. Otherwise, this value will be used as the subdomain.
|
*/
'domain' => env('TELESCOPE_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Telescope Path
|--------------------------------------------------------------------------
|
| This is the URI path where Telescope will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => env('TELESCOPE_PATH', 'telescope'),
/*
|--------------------------------------------------------------------------
| Telescope Storage Driver
|--------------------------------------------------------------------------
|
| This configuration options determines the storage driver that will
| be used to store Telescope's data. In addition, you may set any
| custom options as needed by the particular driver you choose.
|
*/
'driver' => env('TELESCOPE_DRIVER', 'database'),
'storage' => [
'database' => [
'connection' => env('DB_CONNECTION', 'mysql'),
'chunk' => 1000,
],
],
/*
|--------------------------------------------------------------------------
| Telescope Queue
|--------------------------------------------------------------------------
|
| This configuration options determines the queue connection and queue
| which will be used to process ProcessPendingUpdate jobs. This can
| be changed if you would prefer to use a non-default connection.
|
*/
'queue' => [
'connection' => env('TELESCOPE_QUEUE_CONNECTION', null),
'queue' => env('TELESCOPE_QUEUE', null),
'delay' => env('TELESCOPE_QUEUE_DELAY', 10),
],
/*
|--------------------------------------------------------------------------
| Telescope Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will be assigned to every Telescope route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => [
'web',
// Authorize::class,
],
/*
|--------------------------------------------------------------------------
| Allowed / Ignored Paths & Commands
|--------------------------------------------------------------------------
|
| The following array lists the URI paths and Artisan commands that will
| not be watched by Telescope. In addition to this list, some Laravel
| commands, like migrations and queue commands, are always ignored.
|
*/
'only_paths' => [
// 'api/*'
],
'ignore_paths' => [
'livewire*',
'nova-api*',
'pulse*',
],
'ignore_commands' => [
//
],
/*
|--------------------------------------------------------------------------
| Telescope Watchers
|--------------------------------------------------------------------------
|
| The following array lists the "watchers" that will be registered with
| Telescope. The watchers gather the application's profile data when
| a request or task is executed. Feel free to customize this list.
|
*/
'watchers' => [
Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true),
Watchers\CacheWatcher::class => [
'enabled' => env('TELESCOPE_CACHE_WATCHER', true),
'hidden' => [],
],
Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true),
Watchers\CommandWatcher::class => [
'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
'ignore' => [],
],
Watchers\DumpWatcher::class => [
'enabled' => env('TELESCOPE_DUMP_WATCHER', true),
'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false),
],
Watchers\EventWatcher::class => [
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
'ignore' => [],
],
Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true),
Watchers\GateWatcher::class => [
'enabled' => env('TELESCOPE_GATE_WATCHER', true),
'ignore_abilities' => [],
'ignore_packages' => true,
'ignore_paths' => [],
],
Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true),
Watchers\LogWatcher::class => [
'enabled' => env('TELESCOPE_LOG_WATCHER', true),
'level' => 'error',
],
Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true),
Watchers\ModelWatcher::class => [
'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
'events' => ['eloquent.*'],
'hydrations' => true,
],
Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true),
Watchers\QueryWatcher::class => [
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
'ignore_packages' => true,
'ignore_paths' => [],
'slow' => 100,
],
Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true),
Watchers\RequestWatcher::class => [
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
'ignore_http_methods' => [],
'ignore_status_codes' => [],
],
Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true),
Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
],
];
@@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Get the migration connection name.
*/
public function getConnection(): ?string
{
return config('telescope.storage.database.connection');
}
/**
* Run the migrations.
*/
public function up(): void
{
$schema = Schema::connection($this->getConnection());
$schema->create('telescope_entries', function (Blueprint $table) {
$table->bigIncrements('sequence');
$table->uuid('uuid');
$table->uuid('batch_id');
$table->string('family_hash')->nullable();
$table->boolean('should_display_on_index')->default(true);
$table->string('type', 20);
$table->longText('content');
$table->dateTime('created_at')->nullable();
$table->unique('uuid');
$table->index('batch_id');
$table->index('family_hash');
$table->index('created_at');
$table->index(['type', 'should_display_on_index']);
});
$schema->create('telescope_entries_tags', function (Blueprint $table) {
$table->uuid('entry_uuid');
$table->string('tag');
$table->primary(['entry_uuid', 'tag']);
$table->index('tag');
$table->foreign('entry_uuid')
->references('uuid')
->on('telescope_entries')
->onDelete('cascade');
});
$schema->create('telescope_monitoring', function (Blueprint $table) {
$table->string('tag')->primary();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$schema = Schema::connection($this->getConnection());
$schema->dropIfExists('telescope_entries_tags');
$schema->dropIfExists('telescope_entries');
$schema->dropIfExists('telescope_monitoring');
}
};
@@ -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('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('failed_jobs');
}
};
@@ -0,0 +1,82 @@
<?php
use App\Models\Job;
use App\Models\JobInfo;
use App\Models\JobInfoType;
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
{
$newJobId = 3;
Job::forceCreate([
"id" => $newJobId,
"name" => "Hellcase Battles",
"description" => "Envoie les meilleures battles d'Hellcase",
]);
JobInfo::forceCreate([
"key" => "hellcase_battles_discord_webhook_url",
"name" => "Webhook Discord",
"description" => "Le lien discord webhook utilisé pour envoyer les meilleures battles d'Hellcase.\nSi aucun n'est spécifié, le webhook Discord des paramètres généraux sera utilisé.",
"placeholder" => "https://discord.com/api/webhooks/...",
"is_required" => false,
"job_info_type_id" => 4,
"job_id" => $newJobId,
]);
JobInfoType::forceCreate([
"id" => 5,
"name" => "number",
]);
JobInfoType::forceCreate([
"id" => 6,
"name" => "boolean",
]);
JobInfo::forceCreate([
"key" => "hellcase_battles_minimum_value",
"name" => "Valeur minimum des battles",
"description" => "La valeur minimale qu'une battle doit avoir pour être envoyée, en euros.",
"placeholder" => "1000",
"job_info_type_id" => 5,
"job_id" => $newJobId,
]);
JobInfo::forceCreate([
"key" => "hellcase_battles_allow_bots",
"name" => "Autoriser les battles avec bots",
"description" => "Envoyer les battles avec un seul joueur et des bots.",
"is_required" => false,
"job_info_type_id" => 6,
"job_id" => $newJobId,
]);
Schema::create('hellcase_battles', function (Blueprint $table) {
$table->id();
$table->string("battle_id")->unique();
$table->float("value");
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Job::where("id", 3)->delete();
JobInfo::where("job_id", 3)->delete();
JobInfoType::whereIn("id", [5, 6])->delete();
Schema::dropIfExists('hellcase_battles');
}
};
+12
View File
@@ -0,0 +1,12 @@
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work --queue=high,default --tries=3 --timeout=300 --sleep=3 --no-interaction --max-time=3600 --max-jobs=1
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=root
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/worker.log
stopwaitsecs=3600
+7
View File
@@ -0,0 +1,7 @@
[supervisord]
nodaemon=true
user=root
[include]
files = /etc/supervisor/conf.d/*.conf
+7 -1
View File
@@ -3,9 +3,15 @@
# Migrate the database # Migrate the database
php ./artisan migrate --force php ./artisan migrate --force
# Cache
php ./artisan optimize:clear && php ./artisan optimize
# Scheduler
crond -b -L /dev/stdout
# Queue worker # Queue worker
php ./artisan queue:work --queue=high,default --tries=3 --timeout=90 --sleep=3 --daemon --no-interaction & supervisord --nodaemon --configuration /etc/supervisord.conf &
supervisorctl start "laravel-worker:*"
# Start all of the server sumulataneously # Start all of the server sumulataneously
php ./artisan serve --no-interaction -vvv --port=80 --host=0.0.0.0 php ./artisan serve --no-interaction -vvv --port=80 --host=0.0.0.0
+70 -7
View File
@@ -11,6 +11,7 @@
"lucide-react": "^0.474.0", "lucide-react": "^0.474.0",
"lucide-vue-next": "^0.474.0", "lucide-vue-next": "^0.474.0",
"radix-vue": "^1.9.13", "radix-vue": "^1.9.13",
"reka-ui": "^2.1.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },
@@ -21,8 +22,10 @@
"autoprefixer": "^10.4.12", "autoprefixer": "^10.4.12",
"axios": "^1.7.4", "axios": "^1.7.4",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-echo": "^2.0.2",
"laravel-vite-plugin": "^1.2.0", "laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"pusher-js": "^8.4.0",
"sass-embedded": "^1.83.4", "sass-embedded": "^1.83.4",
"tailwindcss": "^3.2.1", "tailwindcss": "^3.2.1",
"typescript": "^5.6.3", "typescript": "^5.6.3",
@@ -956,20 +959,20 @@
} }
}, },
"node_modules/@tanstack/virtual-core": { "node_modules/@tanstack/virtual-core": {
"version": "3.11.3", "version": "3.13.4",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.3.tgz", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.4.tgz",
"integrity": "sha512-v2mrNSnMwnPJtcVqNvV0c5roGCBqeogN8jDtgtuHCphdwBasOZ17x8UV8qpHUh+u0MLfX43c0uUHKje0s+Zb0w==", "integrity": "sha512-fNGO9fjjSLns87tlcto106enQQLycCKR4DPNpgq3djP5IdcPFdPAmaKjsgzIeRhH7hWrELgW12hYnRthS5kLUw==",
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/tannerlinsley" "url": "https://github.com/sponsors/tannerlinsley"
} }
}, },
"node_modules/@tanstack/vue-virtual": { "node_modules/@tanstack/vue-virtual": {
"version": "3.11.3", "version": "3.13.4",
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.11.3.tgz", "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.4.tgz",
"integrity": "sha512-BVZ00i5XBucetRj2doVd32jOPtJthvZSVJvx9GL4gSQsyngliSCtzlP1Op7TFrEtmebRKT8QUQE1tRhOQzWecQ==", "integrity": "sha512-1fPrd3hE1SS4R/9JbX1AlzueY4duCK7ixuLcMW5GMnk9N6WbLo9MioNKiv22V+UaXKOLNy8tLdzT8NYerOFTOQ==",
"dependencies": { "dependencies": {
"@tanstack/virtual-core": "3.11.3" "@tanstack/virtual-core": "3.13.4"
}, },
"funding": { "funding": {
"type": "github", "type": "github",
@@ -2239,6 +2242,15 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/laravel-echo": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.0.2.tgz",
"integrity": "sha512-Ciai6hA7r35MFqNRb8G034cvm9WiveSTFQQKRGJhWtZGbng7C8BBa5QvqDxk/Mw5GeJ+q19jrEwQhf7r1b1lcg==",
"dev": true,
"engines": {
"node": ">=20"
}
},
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz",
@@ -2487,6 +2499,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/ohash": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz",
"integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="
},
"node_modules/package-json-from-dist": { "node_modules/package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -2700,6 +2717,15 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true "dev": true
}, },
"node_modules/pusher-js": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz",
"integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==",
"dev": true,
"dependencies": {
"tweetnacl": "^1.0.3"
}
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.0", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
@@ -2847,6 +2873,37 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/reka-ui": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.1.0.tgz",
"integrity": "sha512-w4kEDEyXhIqv4QeFJeiuBc4mQP37hH/UTRpEb9dMbPdR49JG5TcV/s0+ntNRONUUW4LDLX7E1ZPcwBw5hnu0yw==",
"dependencies": {
"@floating-ui/dom": "^1.6.13",
"@floating-ui/vue": "^1.1.6",
"@internationalized/date": "^3.5.0",
"@internationalized/number": "^3.5.0",
"@tanstack/vue-virtual": "^3.12.0",
"@vueuse/core": "^12.5.0",
"@vueuse/shared": "^12.5.0",
"aria-hidden": "^1.2.4",
"defu": "^6.1.4",
"ohash": "^1.1.4"
},
"peerDependencies": {
"vue": ">= 3.2.0"
}
},
"node_modules/reka-ui/node_modules/@vueuse/shared": {
"version": "12.8.2",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz",
"integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==",
"dependencies": {
"vue": "^3.5.13"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/require-directory": { "node_modules/require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -3697,6 +3754,12 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
}, },
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"dev": true
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.7.3", "version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+4
View File
@@ -12,8 +12,10 @@
"autoprefixer": "^10.4.12", "autoprefixer": "^10.4.12",
"axios": "^1.7.4", "axios": "^1.7.4",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-echo": "^2.0.2",
"laravel-vite-plugin": "^1.2.0", "laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"pusher-js": "^8.4.0",
"sass-embedded": "^1.83.4", "sass-embedded": "^1.83.4",
"tailwindcss": "^3.2.1", "tailwindcss": "^3.2.1",
"typescript": "^5.6.3", "typescript": "^5.6.3",
@@ -22,12 +24,14 @@
"vue-tsc": "^2.0.24" "vue-tsc": "^2.0.24"
}, },
"dependencies": { "dependencies": {
"@tanstack/vue-table": "^8.21.2",
"@vueuse/core": "^12.5.0", "@vueuse/core": "^12.5.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.474.0", "lucide-react": "^0.474.0",
"lucide-vue-next": "^0.474.0", "lucide-vue-next": "^0.474.0",
"radix-vue": "^1.9.13", "radix-vue": "^1.9.13",
"reka-ui": "^2.1.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
} }
+281
View File
@@ -8,6 +8,12 @@ importers:
.: .:
dependencies: dependencies:
'@tanstack/vue-table':
specifier: ^8.21.2
version: 8.21.2(vue@3.5.13(typescript@5.7.3))
'@vueuse/core':
specifier: ^12.5.0
version: 12.7.0(typescript@5.7.3)
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@@ -17,6 +23,15 @@ importers:
lucide-react: lucide-react:
specifier: ^0.474.0 specifier: ^0.474.0
version: 0.474.0(react@19.0.0) version: 0.474.0(react@19.0.0)
lucide-vue-next:
specifier: ^0.474.0
version: 0.474.0(vue@3.5.13(typescript@5.7.3))
radix-vue:
specifier: ^1.9.13
version: 1.9.16(vue@3.5.13(typescript@5.7.3))
reka-ui:
specifier: ^2.1.0
version: 2.1.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
tailwind-merge: tailwind-merge:
specifier: ^2.6.0 specifier: ^2.6.0
version: 2.6.0 version: 2.6.0
@@ -42,12 +57,18 @@ importers:
concurrently: concurrently:
specifier: ^9.0.1 specifier: ^9.0.1
version: 9.1.2 version: 9.1.2
laravel-echo:
specifier: ^2.0.2
version: 2.0.2
laravel-vite-plugin: laravel-vite-plugin:
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0(vite@6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0)) version: 1.2.0(vite@6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0))
postcss: postcss:
specifier: ^8.4.31 specifier: ^8.4.31
version: 8.5.1 version: 8.5.1
pusher-js:
specifier: ^8.4.0
version: 8.4.0
sass-embedded: sass-embedded:
specifier: ^1.83.4 specifier: ^1.83.4
version: 1.83.4 version: 1.83.4
@@ -243,6 +264,18 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@floating-ui/core@1.6.9':
resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
'@floating-ui/dom@1.6.13':
resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
'@floating-ui/vue@1.1.6':
resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==}
'@inertiajs/core@2.0.3': '@inertiajs/core@2.0.3':
resolution: {integrity: sha512-JvXzqc2XAt3WgEDMyxCyXO6bDLMCsBjFsYREU1/+3wtNTib7QKwK71+aF+MrhILpz+kRTi29TsLqnbkPHBAZjw==} resolution: {integrity: sha512-JvXzqc2XAt3WgEDMyxCyXO6bDLMCsBjFsYREU1/+3wtNTib7QKwK71+aF+MrhILpz+kRTi29TsLqnbkPHBAZjw==}
@@ -251,6 +284,12 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
'@internationalized/date@3.7.0':
resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==}
'@internationalized/number@3.6.0':
resolution: {integrity: sha512-PtrRcJVy7nw++wn4W2OuePQQfTqDzfusSuY1QTtui4wa7r+rGVtR75pO8CyKvHvzyQYi3Q1uO5sY0AsB4e65Bw==}
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -384,14 +423,38 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@tailwindcss/forms@0.5.10': '@tailwindcss/forms@0.5.10':
resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
peerDependencies: peerDependencies:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
'@tanstack/table-core@8.21.2':
resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==}
engines: {node: '>=12'}
'@tanstack/virtual-core@3.13.2':
resolution: {integrity: sha512-Qzz4EgzMbO5gKrmqUondCjiHcuu4B1ftHb0pjCut661lXZdGoHeze9f/M8iwsK1t5LGR6aNuNGU7mxkowaW6RQ==}
'@tanstack/vue-table@8.21.2':
resolution: {integrity: sha512-KBgOWxha/x4m1EdhVWxOpqHb661UjqAxzPcmXR3QiA7aShZ547x19Gw0UJX9we+m+tVcPuLRZ61JsYW47QZFfQ==}
engines: {node: '>=12'}
peerDependencies:
vue: '>=3.2'
'@tanstack/vue-virtual@3.13.2':
resolution: {integrity: sha512-z4swzjdhzCh95n9dw9lTvw+t3iwSkYRlVkYkra3C9mul/m5fTzHR7KmtkwH4qXMTXGJUbngtC/bz2cHQIHkO8g==}
peerDependencies:
vue: ^2.7.0 || ^3.0.0
'@types/estree@1.0.6': '@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
'@types/web-bluetooth@0.0.20':
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
'@vitejs/plugin-vue@5.2.1': '@vitejs/plugin-vue@5.2.1':
resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
@@ -448,6 +511,24 @@ packages:
'@vue/shared@3.5.13': '@vue/shared@3.5.13':
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
'@vueuse/core@10.11.1':
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
'@vueuse/core@12.7.0':
resolution: {integrity: sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA==}
'@vueuse/metadata@10.11.1':
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
'@vueuse/metadata@12.7.0':
resolution: {integrity: sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==}
'@vueuse/shared@10.11.1':
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
'@vueuse/shared@12.7.0':
resolution: {integrity: sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw==}
alien-signals@0.4.14: alien-signals@0.4.14:
resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==}
@@ -477,6 +558,10 @@ packages:
arg@5.0.2: arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
aria-hidden@1.2.4:
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
engines: {node: '>=10'}
asynckit@0.4.0: asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -588,6 +673,9 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
delayed-stream@1.0.0: delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
@@ -642,6 +730,9 @@ packages:
estree-walker@2.0.2: estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-glob@3.3.3: fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
@@ -762,6 +853,10 @@ packages:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true hasBin: true
laravel-echo@2.0.2:
resolution: {integrity: sha512-Ciai6hA7r35MFqNRb8G034cvm9WiveSTFQQKRGJhWtZGbng7C8BBa5QvqDxk/Mw5GeJ+q19jrEwQhf7r1b1lcg==}
engines: {node: '>=20'}
laravel-vite-plugin@1.2.0: laravel-vite-plugin@1.2.0:
resolution: {integrity: sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==} resolution: {integrity: sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -794,6 +889,11 @@ packages:
peerDependencies: peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
lucide-vue-next@0.474.0:
resolution: {integrity: sha512-bQaSBjfJ33xiPQCxCf4JD3rcUgZFgWZzxSY8SScNa4Mcq2vWGlbvQx6icTL1UXRqsxzfoT13RXawePSmgg4iWw==}
peerDependencies:
vue: '>=3.0.1'
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -840,6 +940,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
nanoid@5.1.2:
resolution: {integrity: sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g==}
engines: {node: ^18 || >=20}
hasBin: true
node-releases@2.0.19: node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
@@ -863,6 +968,9 @@ packages:
resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
ohash@1.1.6:
resolution: {integrity: sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==}
package-json-from-dist@1.0.1: package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
@@ -939,6 +1047,9 @@ packages:
proxy-from-env@1.1.0: proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
pusher-js@8.4.0:
resolution: {integrity: sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==}
qs@6.14.0: qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
@@ -946,6 +1057,11 @@ packages:
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
radix-vue@1.9.16:
resolution: {integrity: sha512-xwkTfQ7Ub/0XmT40JDc3g03xuYqKIJzVKGazcIkk8mUksj/tbw1pcCVRP0e3hKvPHKeQ0cktI1MvRnlUwCRvoQ==}
peerDependencies:
vue: '>= 3.2.0'
react@19.0.0: react@19.0.0:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -957,6 +1073,11 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
reka-ui@2.1.0:
resolution: {integrity: sha512-w4kEDEyXhIqv4QeFJeiuBc4mQP37hH/UTRpEb9dMbPdR49JG5TcV/s0+ntNRONUUW4LDLX7E1ZPcwBw5hnu0yw==}
peerDependencies:
vue: '>= 3.2.0'
require-directory@2.1.1: require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -1217,6 +1338,9 @@ packages:
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tweetnacl@1.0.3:
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
typescript@5.7.3: typescript@5.7.3:
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
@@ -1280,6 +1404,17 @@ packages:
vscode-uri@3.0.8: vscode-uri@3.0.8:
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
hasBin: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
vue-tsc@2.2.0: vue-tsc@2.2.0:
resolution: {integrity: sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==} resolution: {integrity: sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==}
hasBin: true hasBin: true
@@ -1418,6 +1553,26 @@ snapshots:
'@esbuild/win32-x64@0.24.2': '@esbuild/win32-x64@0.24.2':
optional: true optional: true
'@floating-ui/core@1.6.9':
dependencies:
'@floating-ui/utils': 0.2.9
'@floating-ui/dom@1.6.13':
dependencies:
'@floating-ui/core': 1.6.9
'@floating-ui/utils': 0.2.9
'@floating-ui/utils@0.2.9': {}
'@floating-ui/vue@1.1.6(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@floating-ui/dom': 1.6.13
'@floating-ui/utils': 0.2.9
vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@inertiajs/core@2.0.3': '@inertiajs/core@2.0.3':
dependencies: dependencies:
axios: 1.7.9 axios: 1.7.9
@@ -1435,6 +1590,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
'@internationalized/date@3.7.0':
dependencies:
'@swc/helpers': 0.5.15
'@internationalized/number@3.6.0':
dependencies:
'@swc/helpers': 0.5.15
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
dependencies: dependencies:
string-width: 5.1.2 string-width: 5.1.2
@@ -1533,13 +1696,33 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.32.1': '@rollup/rollup-win32-x64-msvc@4.32.1':
optional: true optional: true
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
'@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)': '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)':
dependencies: dependencies:
mini-svg-data-uri: 1.4.4 mini-svg-data-uri: 1.4.4
tailwindcss: 3.4.17 tailwindcss: 3.4.17
'@tanstack/table-core@8.21.2': {}
'@tanstack/virtual-core@3.13.2': {}
'@tanstack/vue-table@8.21.2(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@tanstack/table-core': 8.21.2
vue: 3.5.13(typescript@5.7.3)
'@tanstack/vue-virtual@3.13.2(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@tanstack/virtual-core': 3.13.2
vue: 3.5.13(typescript@5.7.3)
'@types/estree@1.0.6': {} '@types/estree@1.0.6': {}
'@types/web-bluetooth@0.0.20': {}
'@vitejs/plugin-vue@5.2.1(vite@6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))': '@vitejs/plugin-vue@5.2.1(vite@6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))':
dependencies: dependencies:
vite: 6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0) vite: 6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0)
@@ -1629,6 +1812,42 @@ snapshots:
'@vue/shared@3.5.13': {} '@vue/shared@3.5.13': {}
'@vueuse/core@10.11.1(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 10.11.1
'@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.7.3))
vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/core@12.7.0(typescript@5.7.3)':
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 12.7.0
'@vueuse/shared': 12.7.0(typescript@5.7.3)
vue: 3.5.13(typescript@5.7.3)
transitivePeerDependencies:
- typescript
'@vueuse/metadata@10.11.1': {}
'@vueuse/metadata@12.7.0': {}
'@vueuse/shared@10.11.1(vue@3.5.13(typescript@5.7.3))':
dependencies:
vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/shared@12.7.0(typescript@5.7.3)':
dependencies:
vue: 3.5.13(typescript@5.7.3)
transitivePeerDependencies:
- typescript
alien-signals@0.4.14: {} alien-signals@0.4.14: {}
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
@@ -1650,6 +1869,10 @@ snapshots:
arg@5.0.2: {} arg@5.0.2: {}
aria-hidden@1.2.4:
dependencies:
tslib: 2.8.1
asynckit@0.4.0: {} asynckit@0.4.0: {}
autoprefixer@10.4.20(postcss@8.5.1): autoprefixer@10.4.20(postcss@8.5.1):
@@ -1772,6 +1995,8 @@ snapshots:
deepmerge@4.3.1: {} deepmerge@4.3.1: {}
defu@6.1.4: {}
delayed-stream@1.0.0: {} delayed-stream@1.0.0: {}
didyoumean@1.2.2: {} didyoumean@1.2.2: {}
@@ -1834,6 +2059,8 @@ snapshots:
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3: fast-glob@3.3.3:
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@@ -1949,6 +2176,8 @@ snapshots:
jiti@1.21.7: {} jiti@1.21.7: {}
laravel-echo@2.0.2: {}
laravel-vite-plugin@1.2.0(vite@6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0)): laravel-vite-plugin@1.2.0(vite@6.0.11(jiti@1.21.7)(sass-embedded@1.83.4)(yaml@2.7.0)):
dependencies: dependencies:
picocolors: 1.1.1 picocolors: 1.1.1
@@ -1971,6 +2200,10 @@ snapshots:
dependencies: dependencies:
react: 19.0.0 react: 19.0.0
lucide-vue-next@0.474.0(vue@3.5.13(typescript@5.7.3)):
dependencies:
vue: 3.5.13(typescript@5.7.3)
magic-string@0.30.17: magic-string@0.30.17:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
@@ -2008,6 +2241,8 @@ snapshots:
nanoid@3.3.8: {} nanoid@3.3.8: {}
nanoid@5.1.2: {}
node-releases@2.0.19: {} node-releases@2.0.19: {}
normalize-path@3.0.0: {} normalize-path@3.0.0: {}
@@ -2020,6 +2255,8 @@ snapshots:
object-inspect@1.13.3: {} object-inspect@1.13.3: {}
ohash@1.1.6: {}
package-json-from-dist@1.0.1: {} package-json-from-dist@1.0.1: {}
path-browserify@1.0.1: {} path-browserify@1.0.1: {}
@@ -2080,12 +2317,33 @@ snapshots:
proxy-from-env@1.1.0: {} proxy-from-env@1.1.0: {}
pusher-js@8.4.0:
dependencies:
tweetnacl: 1.0.3
qs@6.14.0: qs@6.14.0:
dependencies: dependencies:
side-channel: 1.1.0 side-channel: 1.1.0
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
radix-vue@1.9.16(vue@3.5.13(typescript@5.7.3)):
dependencies:
'@floating-ui/dom': 1.6.13
'@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.7.3))
'@internationalized/date': 3.7.0
'@internationalized/number': 3.6.0
'@tanstack/vue-virtual': 3.13.2(vue@3.5.13(typescript@5.7.3))
'@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.7.3))
'@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.7.3))
aria-hidden: 1.2.4
defu: 6.1.4
fast-deep-equal: 3.1.3
nanoid: 5.1.2
vue: 3.5.13(typescript@5.7.3)
transitivePeerDependencies:
- '@vue/composition-api'
react@19.0.0: {} react@19.0.0: {}
read-cache@1.0.0: read-cache@1.0.0:
@@ -2096,6 +2354,23 @@ snapshots:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
reka-ui@2.1.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)):
dependencies:
'@floating-ui/dom': 1.6.13
'@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.7.3))
'@internationalized/date': 3.7.0
'@internationalized/number': 3.6.0
'@tanstack/vue-virtual': 3.13.2(vue@3.5.13(typescript@5.7.3))
'@vueuse/core': 12.7.0(typescript@5.7.3)
'@vueuse/shared': 12.7.0(typescript@5.7.3)
aria-hidden: 1.2.4
defu: 6.1.4
ohash: 1.1.6
vue: 3.5.13(typescript@5.7.3)
transitivePeerDependencies:
- '@vue/composition-api'
- typescript
require-directory@2.1.1: {} require-directory@2.1.1: {}
resolve@1.22.10: resolve@1.22.10:
@@ -2368,6 +2643,8 @@ snapshots:
tslib@2.8.1: {} tslib@2.8.1: {}
tweetnacl@1.0.3: {}
typescript@5.7.3: {} typescript@5.7.3: {}
update-browserslist-db@1.1.2(browserslist@4.24.4): update-browserslist-db@1.1.2(browserslist@4.24.4):
@@ -2398,6 +2675,10 @@ snapshots:
vscode-uri@3.0.8: {} vscode-uri@3.0.8: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.7.3)):
dependencies:
vue: 3.5.13(typescript@5.7.3)
vue-tsc@2.2.0(typescript@5.7.3): vue-tsc@2.2.0(typescript@5.7.3):
dependencies: dependencies:
'@volar/typescript': 2.4.11 '@volar/typescript': 2.4.11
+1
View File
@@ -0,0 +1 @@
/home/ninluc/Documents/codage/DatBrowser/app/Browser/console
+1
View File
@@ -0,0 +1 @@
/home/ninluc/Documents/codage/DatBrowser/app/Browser/downloads
Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
/home/ninluc/Documents/codage/DatBrowser/app/Browser/screenshots
+1
View File
@@ -0,0 +1 @@
/home/ninluc/Documents/codage/DatBrowser/app/Browser/source
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+5
View File
@@ -0,0 +1,5 @@
{
"/app.js": "/app.js?id=a04a99f77a55ffcecde23cd7304b481b",
"/app-dark.css": "/app-dark.css?id=1ea407db56c5163ae29311f1f38eb7b9",
"/app.css": "/app.css?id=de4c978567bfd90b38d186937dee5ccf"
}
@@ -18,7 +18,7 @@ const jobInfoType = props.jobInfo.job_info_type.name;
<div> <div>
<Label :for="'' + jobInfo.id" class="text">{{ jobInfo.name }}<span v-if="jobInfo.is_required" class="cursor-help" title="Requis" aria-label="Requis">*</span></Label> <Label :for="'' + jobInfo.id" class="text">{{ jobInfo.name }}<span v-if="jobInfo.is_required" class="cursor-help" title="Requis" aria-label="Requis">*</span></Label>
<Description>{{ jobInfo.description }}</Description> <Description>{{ jobInfo.description }}</Description>
<Input v-if="jobInfoType != 'checkbox'" :type="jobInfoType" :id="'' + jobInfo.id" :name="'' + jobInfo.id" :placeholder="jobInfo.placeholder" v-model="jobInfo.value as string" :required="jobInfo.is_required" /> <Input v-if="['text', 'email', 'password', 'url', 'number'].includes(jobInfoType)" :type="jobInfoType" :id="'' + jobInfo.id" :name="'' + jobInfo.id" :placeholder="jobInfo.placeholder" v-model="jobInfo.value as string" :required="jobInfo.is_required" />
<VModelCheckbox v-else :id="'' + jobInfo.id" :class="''" v-model="jobInfo.value as boolean" /> <VModelCheckbox v-else :id="'' + jobInfo.id" :class="''" v-model="jobInfo.value as boolean" />
</div> </div>
@@ -0,0 +1,24 @@
<script setup lang="ts">
import Separator from "@/Components/ui/separator/Separator.vue";
import { JobRunArtifact } from "@/types/Jobs/job";
defineProps<{
jobRun: JobRunArtifact;
}>();
</script>
<template>
<ul>
<li v-for="artifact in jobRun.artifacts" :key="artifact.id">
<p>{{ artifact.name }}</p>
<p class="italic">{{ artifact.content }}</p>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
list-style-type: circle;
padding-left: 1rem;
}
</style>
@@ -0,0 +1,28 @@
<script setup lang="ts">
import AccordionContent from "@/Components/ui/accordion/AccordionContent.vue";
import AccordionItem from "@/Components/ui/accordion/AccordionItem.vue";
import AccordionTrigger from "@/Components/ui/accordion/AccordionTrigger.vue";
import { JobRunArtifact } from "@/types/Jobs/job";
import JobRunArtifacts from "./JobRunArtifacts.vue";
defineProps<{
jobRun: JobRunArtifact;
}>();
</script>
<template>
<AccordionItem :value="''+jobRun.id" :class="[jobRun.success ? 'bg-green-100' : 'bg-red-200', 'first:rounded-t last:rounded-b', 'px-3']">
<AccordionTrigger>
{{ new Date(Date.parse(jobRun.created_at)).toLocaleTimeString(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})
}}
</AccordionTrigger>
<AccordionContent>
<JobRunArtifacts :jobRun="jobRun" />
</AccordionContent>
</AccordionItem>
</template>
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { Job, JobRunArtifact } from "@/types/Jobs/job";
import JobRunItem from "./JobRunItem.vue";
import Accordion from "@/Components/ui/accordion/Accordion.vue";
import ScrollArea from "@/Components/ui/scroll-area/ScrollArea.vue";
defineProps<{
job: Job;
}>();
</script>
<template>
<div v-if="job.job_runs.length > 0">
<h2>Ancien jobs</h2>
<ScrollArea class="min-h-[300px] max-h-[20vh] overflow-auto pr-2">
<Accordion type="multiple" collapsible>
<JobRunItem
:jobRun="jobRun"
v-for="jobRun in job.job_runs.sort((a, b) => {
return (
new Date(b.created_at).getTime() -
new Date(a.created_at).getTime()
);
})"
:key="jobRun.id"
/>
</Accordion>
</ScrollArea>
</div>
</template>
@@ -0,0 +1,19 @@
<script setup lang="ts">
import {
AccordionRoot,
type AccordionRootEmits,
type AccordionRootProps,
useForwardPropsEmits,
} from 'reka-ui'
const props = defineProps<AccordionRootProps>()
const emits = defineEmits<AccordionRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<AccordionRoot v-bind="forwarded">
<slot />
</AccordionRoot>
</template>
@@ -0,0 +1,24 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { AccordionContent, type AccordionContentProps } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AccordionContent
v-bind="delegatedProps"
class="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
>
<div :class="cn('pb-4 pt-0', props.class)">
<slot />
</div>
</AccordionContent>
</template>
@@ -0,0 +1,24 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<AccordionItem
v-bind="forwardedProps"
:class="cn('border-b', props.class)"
>
<slot />
</AccordionItem>
</template>
@@ -0,0 +1,39 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { ChevronDown } from 'lucide-vue-next'
import {
AccordionHeader,
AccordionTrigger,
type AccordionTriggerProps,
} from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AccordionHeader class="flex">
<AccordionTrigger
v-bind="delegatedProps"
:class="
cn(
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
props.class,
)
"
>
<slot />
<slot name="icon">
<ChevronDown
class="h-4 w-4 shrink-0 transition-transform duration-200"
/>
</slot>
</AccordionTrigger>
</AccordionHeader>
</template>
@@ -0,0 +1,4 @@
export { default as Accordion } from './Accordion.vue'
export { default as AccordionContent } from './AccordionContent.vue'
export { default as AccordionItem } from './AccordionItem.vue'
export { default as AccordionTrigger } from './AccordionTrigger.vue'
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { Separator, type SeparatorProps } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<
SeparatorProps & { class?: HTMLAttributes['class'], label?: string }
>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<Separator
v-bind="delegatedProps"
:class="
cn(
'shrink-0 bg-border relative',
props.orientation === 'vertical' ? 'w-px h-full' : 'h-px w-full',
props.class,
)
"
>
<span
v-if="props.label"
:class="cn('text-xs text-muted-foreground bg-background absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex justify-center items-center',
props.orientation === 'vertical' ? 'w-[1px] px-1 py-2' : 'h-[1px] py-1 px-2',
)"
>{{ props.label }}</span>
</Separator>
</template>
@@ -0,0 +1 @@
export { default as Separator } from './Separator.vue'
+1 -1
View File
@@ -11,7 +11,7 @@ const jobs = ref<Job[]>([]);
async function fetchJobs() { async function fetchJobs() {
let jobsRaw = await httpApi<Job[]>("/jobs"); let jobsRaw = await httpApi<Job[]>("/jobs");
jobs.value = jobsRaw.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); jobs.value = jobsRaw.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
} }
onMounted(fetchJobs); onMounted(fetchJobs);
+4 -1
View File
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import JobForm from '../Components/Layout/Job/JobForm.vue' import JobForm from '../Components/Layout/Job/JobForm.vue'
import JobCard from '../Components/Layout/Job/JobCard.vue' import JobCard from '../Components/Layout/Job/JobCard.vue'
import JobRuns from '../Components/Layout/Job/JobRuns/JobRuns.vue';
import { Job } from "@/types/Jobs/job"; import { Job } from "@/types/Jobs/job";
import { Head } from "@inertiajs/vue3"; import { Head } from "@inertiajs/vue3";
@@ -11,9 +12,11 @@ defineProps<{
</script> </script>
<template> <template>
<Head title="Welcome" /> <Head :title="job.name" />
<JobCard :job="job" /> <JobCard :job="job" />
<JobForm :job="job" :error="error" /> <JobForm :job="job" :error="error" />
<JobRuns :job="job" />
</template> </template>
+14
View File
@@ -0,0 +1,14 @@
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
+12 -3
View File
@@ -1,8 +1,17 @@
import { clsx, type ClassValue } from "clsx"; import type { Updater } from '@tanstack/vue-table'
import { twMerge } from "tailwind-merge"; import type { Ref } from 'vue'
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs))
}
export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) {
ref.value
= typeof updaterOrValue === 'function'
? updaterOrValue(ref.value)
: updaterOrValue
} }
export async function httpApi<T>(route: string): Promise<T> { export async function httpApi<T>(route: string): Promise<T> {
+11 -3
View File
@@ -5,8 +5,9 @@ export type Job = {
is_active: boolean; is_active: boolean;
job_infos: JobInfo[]; job_infos: JobInfo[];
job_runs: JobRunArtifact[];
created_at: Date; created_at: string;
} }
export type JobInfo = { export type JobInfo = {
@@ -26,16 +27,23 @@ export type JobInfo = {
export type JobInfoType = { export type JobInfoType = {
id: number; id: number;
name: string; name: string;
created_at: Date;
created_at: string;
} }
export type JobRunArtifact = { export type JobRunArtifact = {
jobId: number; id: number;
job_id: number;
artifacts: JobArtifact[]; artifacts: JobArtifact[];
success: boolean; success: boolean;
created_at: string;
} }
export type JobArtifact = { export type JobArtifact = {
id: number;
name: string; name: string;
content: string; content: string;
created_at: string;
} }
+1
View File
@@ -12,6 +12,7 @@ Route::get('/jobs', function (Request $request) {
Route::get('/test/{id}', function (Request $request, $id, BrowserJobsInstances $BrowserJobsInstances) { Route::get('/test/{id}', function (Request $request, $id, BrowserJobsInstances $BrowserJobsInstances) {
$log = $BrowserJobsInstances->getJobInstance($id)->execute(); $log = $BrowserJobsInstances->getJobInstance($id)->execute();
dump($log);
return response()->json(['message' => 'Job ' . $id . ' ran', 'jobRun' => $log->load('artifacts')]); return response()->json(['message' => 'Job ' . $id . ' ran', 'jobRun' => $log->load('artifacts')]);
}); });
+4
View File
@@ -0,0 +1,4 @@
<?php
use Illuminate\Support\Facades\Broadcast;
+12 -1
View File
@@ -1,7 +1,8 @@
<?php <?php
use App\Browser\Jobs\Hellcase\HellcaseJob; use App\Browser\Jobs\Hellcase\HellcaseJob;
use App\Models\Job; use App\Browser\Jobs\HellcaseBattles\HellcaseBattlesJob;
use App\Jobs\PruneOldJobRuns;
use App\Services\BrowserJobsInstances; use App\Services\BrowserJobsInstances;
use Illuminate\Foundation\Inspiring; use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
@@ -10,5 +11,15 @@ Artisan::command('inspire', function () {
$this->comment(Inspiring::quote()); $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote'); })->purpose('Display an inspiring quote');
// Telescope
Schedule::command('telescope:prune')->monthly();
// Prune old job runs
Schedule::job(new PruneOldJobRuns)->monthly()->onOneServer()->withoutOverlapping()->name('prune-old-job-runs')->description('Prune old job runs')->skip(function () {
return !config('jobs.pruneOldJobRuns.enabled');
});
// Jobs
Schedule::job(new HellcaseJob)->daily()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job'); Schedule::job(new HellcaseJob)->daily()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job');
// Schedule::job(new HellcaseJob)->everyMinute()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job'); // Schedule::job(new HellcaseJob)->everyMinute()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job');
Schedule::job(new HellcaseBattlesJob)->hourly()->onOneServer()->withoutOverlapping()->name('hellcase_battles')->description('Hellcase battles job');
+23
View File
@@ -0,0 +1,23 @@
# TODO
- Fix hellcase, fermer lespopups à chaque nouvelle visite
- Voir si le scheduler fonctionne au démmarage
- Mettre un timeout pour pas overwhelm le pc au démmarage
- Image pour le join de giveaway
- → Notification
- Ou ajouter des images base64 dans les jobRun
- Risque de devenir volumineux
- Sauf avec le job qui enlève les vieilles jobRun
- Notification live websocket
- Websocket installé
- Serveur php plus propre (nginx, apache, n'importe)
- Epic games
Pas l'air possible avec cloudflare
- Petit bug, quand l'on enregistre un formulaire avec une erreur, l'url a un argument GET ?error=mon%24erreur
Du coup dans la nav le job actuel n'est plus reconnu
## Pour deploy Lama
- Version Lama du compose
- Tuto installation
Binary file not shown.
+20 -1
View File
@@ -1 +1,20 @@
sudo docker run --rm -it -p 3389:3389 -v ./undetectedChromedriver:/root/.local/share/undetected_chromedriver/ ultrafunk/undetected-chromedriver:latest #!/bin/bash
# From undetected chromedriver docker
#sudo docker run --rm -it -p 3389:3389 -v ./undetectedChromedriver:/root/.local/share/undetected_chromedriver/ ultrafunk/undetected-chromedriver:latest
# With undetected chromedriver patcher
# Run the selenium/standalone-chrome:latest with a specific container name in the background
sudo docker run -d --name standalone-chrome selenium/standalone-chrome:latest
sleep 5
# Copy the chromedriver binary from the container to the host
sudo docker cp -L standalone-chrome:/bin/chromedriver ./chromedriver
# Stop the container
sudo docker stop standalone-chrome
sudo chmod 777 ./chromedriver
# Patch the chromedriver binary
python3 ./patchChromedriver.py
@@ -0,0 +1,8 @@
#!/bin/python3
import undetected_chromedriver as uc
options = uc.ChromeOptions()
# Chromedriver is in current directory
driver = uc.Chrome(options = options, browser_executable_path="/usr/bin/google-chrome", driver_executable_path="/home/ninluc/Documents/codage/DatBrowser/undetectedChromedriver/chromedriver")
driver.get('https://nowsecure.nl')
@@ -1,6 +1,7 @@
FROM selenium/standalone-chrome:108.0 AS final # FROM selenium/standalone-chrome:108.0 AS final
FROM selenium/standalone-chrome:latest AS final
COPY undetectedChromedriver/chromedriver-linux /bin/chromedriver COPY undetectedChromedriver/chromedriver /bin/chromedriver
RUN mkdir -p /home/seluser/profile/ RUN mkdir -p /home/seluser/profile/
ENV TZ=Europe/Brussels ENV TZ=Europe/Brussels
-1
View File
@@ -3,7 +3,6 @@ import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
export default defineConfig({ export default defineConfig({
base: process.env.ASSET_URL || '/',
plugins: [ plugins: [
laravel({ laravel({
input: 'resources/js/app.ts', input: 'resources/js/app.ts',