feat: add tests, chore: update readme
51
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: Testing
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: 8.4
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: xdebug
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install Node Dependencies
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||||
|
|
||||||
|
- name: Copy Environment File
|
||||||
|
run: cp .env.example .env
|
||||||
|
|
||||||
|
- name: Generate Application Key
|
||||||
|
run: php artisan key:generate
|
||||||
|
|
||||||
|
- name: Build Assets
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: ./vendor/bin/pest
|
||||||
191
README.md
|
|
@ -1,10 +1,24 @@
|
||||||
## Laravel Trmnl Server
|
## Laravel Trmnl Server
|
||||||
|
|
||||||
This is a PoC of a TRMNL server, written in Laravel. Inspired by https://github.com/usetrmnl/byos_sinatra
|
Laravel Trmnl Server is a self-hostable implementation of a TRMNL server, built with Laravel.
|
||||||
|
It enables you to manage TRMNL devices, generate screens dynamically, and can act as a proxy for the TRMNL API (native plugin system).
|
||||||
|
Inspired by [usetrmnl/byos_sinatra](https://github.com/usetrmnl/byos_sinatra).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
[More Screenshots](screenshots/SCREENSHOTS.md)
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
* 📡 Device Information – Display battery status, WiFi strength, firmware version, and more.
|
||||||
|
* 🔍 Auto-Join – Automatically detects and adds devices from your local network.
|
||||||
|
* 🖥️ Screen Generation – Supports Markup, API, or update via Code.
|
||||||
|
* 🔄 TRMNL API Proxy – Can act as a proxy for TRMNL API (requires TRMNL Developer Edition).
|
||||||
|
* This enables a hybrid setup – for example, you can update your custom Train Monitor every 5 minutes in the morning, while displaying native TRMNL plugins throughout the day.
|
||||||
|
* 🐳 Deployment – Dockerized setup for easier hosting (Dockerfile, docker-compose).
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
* PHP >= 8.2
|
* PHP >= 8.2
|
||||||
* ext-imagick
|
* ext-imagick
|
||||||
* puppeteer [see Browsershot docs](https://spatie.be/docs/browsershot/v4/requirements)
|
* puppeteer [see Browsershot docs](https://spatie.be/docs/browsershot/v4/requirements)
|
||||||
|
|
@ -45,31 +59,190 @@ To make your server accessible in the network, you can run the following command
|
||||||
php artisan serve --host=0.0.0.0 --port 4567
|
php artisan serve --host=0.0.0.0 --port 4567
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
Use the provided Dockerfile, or docker-compose file to run the server in a container.
|
||||||
|
You can persist the database file by mounting a volume to `/var/www/html/database/database.sqlite`.
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# docker-compose.yaml
|
||||||
|
volumes:
|
||||||
|
- ./database/database.sqlite:/var/www/html/database/database.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
|
#### Environment Variables
|
||||||
|
|
||||||
|
| environment | description | default |
|
||||||
|
|-------------------------------|------------------------------------------------------------------|-------------------|
|
||||||
|
| `TRMNL_PROXY_BASE_URL` | Base URL of the native TRMNL service | https://trmnl.app |
|
||||||
|
| `TRMNL_PROXY_REFRESH_MINUTES` | How often should the server fetch new images from native service | 15 |
|
||||||
|
| `REGISTRATION_ENABLED` | Allow user registration via Webinterface | 1 |
|
||||||
|
|
||||||
|
#### Login
|
||||||
|
|
||||||
If your environment is local, you can access the server at `http://localhost:4567` and login with user / password
|
If your environment is local, you can access the server at `http://localhost:4567` and login with user / password
|
||||||
admin@example.com / admin@example.com
|
`admin@example.com` / `admin@example.com`, otherwise register. With environment variable `REGISTRATION_ENABLED` you can control, if registration is allowed.
|
||||||
|
|
||||||
#### Add your TRMNL Device
|
#### ➕ Add Your TRMNL Device
|
||||||
|
|
||||||
http://localhost:4567/devices
|
##### Auto-Join (Local Network)
|
||||||
|
|
||||||
* Add new device
|
1. Switch on the “Permit Auto-Join” toggle in the header. For that to work only one user can be registered.
|
||||||
* You can grab the TRMNL Mac Address and API Key from the TRMNL Dashboard. Or debug the incoming request to `/api/setup` to determine.
|
2. New devices on your local network will be detected and added automatically when connecting to the server.
|
||||||
|
|
||||||
|
✅ This is the easiest way to connect your devices with minimal effort.
|
||||||
|
|
||||||
|
##### Manually
|
||||||
|
|
||||||
|
1. Open the Devices page:
|
||||||
|
👉 http://localhost:4567/devices
|
||||||
|
2. Click “Add New Device”.
|
||||||
|
3. Retrieve your TRMNL MAC Address and API Key:
|
||||||
|
- You can grab the TRMNL Mac Address and API Key from the TRMNL Dashboard
|
||||||
|
- Alternatively, debug incoming requests to /api/setup to determine them
|
||||||
|
|
||||||
|
### ⚙️ Configure Server for Device
|
||||||
|
|
||||||
|
#### 📌 Firmware Version 1.4.6 or Newer
|
||||||
|
|
||||||
|
* Setup device
|
||||||
|
* After entering Wifi credentials, choose "Custom Server"
|
||||||
|
* Point to the URL of your server
|
||||||
|
|
||||||
|
#### Firmware Older Than 1.4.6
|
||||||
|
|
||||||
|
If your device firmware is older than 1.4.6, you need to flash a new firmware version to point it to your server.
|
||||||
|
|
||||||
#### Flash Firmware to point Device to your server
|
|
||||||
See this YouTube guide: [https://www.youtube.com/watch?v=3xehPW-PCOM](https://www.youtube.com/watch?v=3xehPW-PCOM)
|
See this YouTube guide: [https://www.youtube.com/watch?v=3xehPW-PCOM](https://www.youtube.com/watch?v=3xehPW-PCOM)
|
||||||
|
|
||||||
#### Generate Screen
|
### 🖥️ Generate Screens
|
||||||
|
|
||||||
* Edit resources/views/trmnl.blade.php
|
#### 🎨 Blade View
|
||||||
|
* Edit `resources/views/trmnl.blade.php`
|
||||||
|
* Available Blade Components are listed here: [laravel-trmnl | Blade Components](https://github.com/bnussbau/laravel-trmnl/tree/main/resources/views/components)
|
||||||
* To generate the screen, run
|
* To generate the screen, run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php artisan trmnl:screen:generate
|
php artisan trmnl:screen:generate
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Generate via API
|
||||||
|
You can dynamically update screens by sending a POST request.
|
||||||
|
|
||||||
|
* Send a POST request to `/api/screen` with the following payload
|
||||||
|
|
||||||
|
##### Header
|
||||||
|
|
||||||
|
`Authorization` `Bearer <TOKEN>`
|
||||||
|
|
||||||
|
##### Body
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"markup": "<h1>Hello World</h1>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Token can be retrieved under Plugins > API in the Web Interface.
|
||||||
|
|
||||||
|
#### Markup via Web Interface
|
||||||
|
|
||||||
|
1. Navigate to Plugins > Markup in the Web Interface.
|
||||||
|
2. Enter your markup manually or select from the available templates.
|
||||||
|
3. Save and apply the changes.
|
||||||
|
|
||||||
|
* Available Blade Components are listed here: [laravel-trmnl | Blade Components](https://github.com/bnussbau/laravel-trmnl/tree/main/resources/views/components)
|
||||||
|
|
||||||
|
#### 🛠️ Generate Screens Programmatically
|
||||||
|
|
||||||
|
You can fetch external data, process it, and generate screens dynamically.
|
||||||
|
* Fetch data from an external source.
|
||||||
|
* Either render it in a Blade view or directly insert markup.
|
||||||
|
* Use Laravel’s scheduler to automate updates.
|
||||||
|
|
||||||
|
#### 📌 Example: Fetch Train Monitor Data
|
||||||
|
|
||||||
|
This example retrieves data from [trmnl-train-monitor](https://github.com/bnussbau/trmnl-train-monitor) and updates the screen periodically.
|
||||||
|
|
||||||
|
##### Step 1: Create a new Artisan Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan make:command PluginTrainMonitorFetch
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 2: Edit PluginTrainMonitorFetch.php
|
||||||
|
|
||||||
|
```php
|
||||||
|
class PluginTrainMonitorFetch extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'plugin:train:fetch';
|
||||||
|
|
||||||
|
protected $description = 'Command description';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$markup = Http::get('https://oebb.trmnl.yourserver.at/markup')->json('markup');
|
||||||
|
GenerateScreenJob::dispatchSync(1, $markup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 3: Schedule the Command in console.php
|
||||||
|
|
||||||
|
```php
|
||||||
|
Schedule::command('plugin:train:fetch')
|
||||||
|
->everyFiveMinutes()
|
||||||
|
->timezone('Europe/Vienna')
|
||||||
|
->between('5:00', '18:00');
|
||||||
|
```
|
||||||
|
|
||||||
|
This will automatically update the screen every 5 minutes between 5:00 AM and 6:00 PM local time.
|
||||||
|
|
||||||
|
### 🤝 Contribution
|
||||||
|
Contributions are welcome! If you’d like to improve the project, follow these steps:
|
||||||
|
|
||||||
|
1. Open an Issue
|
||||||
|
- Before submitting a pull request, create an issue to discuss your idea.
|
||||||
|
- Clearly describe the feature or bug fix you want to work on.
|
||||||
|
2. Fork the Repository & Create a Branch
|
||||||
|
3. Make Your Changes & Add Tests
|
||||||
|
- Ensure your code follows best practices.
|
||||||
|
- Add Pest tests to cover your changes.
|
||||||
|
4. Run Tests
|
||||||
|
- `php artisan test`
|
||||||
|
5. Submit a Pull Request (PR)
|
||||||
|
- Push your branch and create a PR.
|
||||||
|
- Provide a clear description of your changes.
|
||||||
|
|
||||||
|
🚀 Thank you for contributing! Every contribution helps improve the project.
|
||||||
|
|
||||||
|
### 🏗️ Roadmap
|
||||||
|
|
||||||
|
Here are some features and improvements that are open for contribution:
|
||||||
|
|
||||||
|
##### 🔌 Plugin System
|
||||||
|
|
||||||
|
- Enable configurable plugins via the Web Interface.
|
||||||
|
- Ensure compatibility with the trmnl-laravel package.
|
||||||
|
- Implement auto-discovery for plugins.
|
||||||
|
|
||||||
|
##### ⏳ Scheduling
|
||||||
|
|
||||||
|
- Move task scheduling from console.php to a Web Interface.
|
||||||
|
- Allow users to configure custom schedule intervals.
|
||||||
|
|
||||||
|
##### 🖥️ “Native” Plugins
|
||||||
|
- Add built-in plugins such as (as an example):
|
||||||
|
- ☁️ Weather
|
||||||
|
- 💬 Quotes
|
||||||
|
- 🏡 Home Assistant integration
|
||||||
|
- Provide Web UI controls to enable/disable plugins.
|
||||||
|
|
||||||
|
##### Improve Code Coverage
|
||||||
|
|
||||||
|
- Expand Pest tests to cover more functionality.
|
||||||
|
- Increase code coverage (currently at 86.9%).
|
||||||
|
|
||||||
### License
|
### License
|
||||||
MIT
|
MIT
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 210 KiB |
|
|
@ -19,7 +19,8 @@
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"livewire/flux": "^2.0",
|
"livewire/flux": "^2.0",
|
||||||
"livewire/volt": "^1.6.7",
|
"livewire/volt": "^1.6.7",
|
||||||
"spatie/browsershot": "^5.0"
|
"spatie/browsershot": "^5.0",
|
||||||
|
"spatie/pest-expectations": "^1.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
|
|
||||||
155
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "2820695c16f3a51b2464c1ed739338ba",
|
"content-hash": "a37113926b52744df508a5619719b4dc",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bnussbau/laravel-trmnl",
|
"name": "bnussbau/laravel-trmnl",
|
||||||
|
|
@ -87,16 +87,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/brick/math.git",
|
"url": "https://github.com/brick/math.git",
|
||||||
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40"
|
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40",
|
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
|
||||||
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40",
|
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -135,7 +135,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/brick/math/issues",
|
"issues": "https://github.com/brick/math/issues",
|
||||||
"source": "https://github.com/brick/math/tree/0.12.2"
|
"source": "https://github.com/brick/math/tree/0.12.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-02-26T10:21:45+00:00"
|
"time": "2025-02-28T13:11:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "carbonphp/carbon-doctrine-types",
|
"name": "carbonphp/carbon-doctrine-types",
|
||||||
|
|
@ -1203,16 +1203,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "intervention/image",
|
"name": "intervention/image",
|
||||||
"version": "3.11.1",
|
"version": "3.11.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Intervention/image.git",
|
"url": "https://github.com/Intervention/image.git",
|
||||||
"reference": "0f87254688e480fbb521e2a1ac6c11c784ca41af"
|
"reference": "ebbb711871fb261c064cf4c422f5f3c124fe1842"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/Intervention/image/zipball/0f87254688e480fbb521e2a1ac6c11c784ca41af",
|
"url": "https://api.github.com/repos/Intervention/image/zipball/ebbb711871fb261c064cf4c422f5f3c124fe1842",
|
||||||
"reference": "0f87254688e480fbb521e2a1ac6c11c784ca41af",
|
"reference": "ebbb711871fb261c064cf4c422f5f3c124fe1842",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -1223,7 +1223,7 @@
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"phpstan/phpstan": "^2.1",
|
"phpstan/phpstan": "^2.1",
|
||||||
"phpunit/phpunit": "^10.0 || ^11.0",
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
"slevomat/coding-standard": "~8.0",
|
"slevomat/coding-standard": "~8.0",
|
||||||
"squizlabs/php_codesniffer": "^3.8"
|
"squizlabs/php_codesniffer": "^3.8"
|
||||||
},
|
},
|
||||||
|
|
@ -1259,7 +1259,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/Intervention/image/issues",
|
"issues": "https://github.com/Intervention/image/issues",
|
||||||
"source": "https://github.com/Intervention/image/tree/3.11.1"
|
"source": "https://github.com/Intervention/image/tree/3.11.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -1275,7 +1275,7 @@
|
||||||
"type": "ko_fi"
|
"type": "ko_fi"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-02-01T07:28:26+00:00"
|
"time": "2025-02-27T13:08:55+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
|
|
@ -2295,16 +2295,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/flux",
|
"name": "livewire/flux",
|
||||||
"version": "v2.0.3",
|
"version": "v2.0.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/livewire/flux.git",
|
"url": "https://github.com/livewire/flux.git",
|
||||||
"reference": "dec010f09419cd9d9930abc4b304802c379be57e"
|
"reference": "6b0d59040715f072982bfc92fe71414b44d45a0c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/livewire/flux/zipball/dec010f09419cd9d9930abc4b304802c379be57e",
|
"url": "https://api.github.com/repos/livewire/flux/zipball/6b0d59040715f072982bfc92fe71414b44d45a0c",
|
||||||
"reference": "dec010f09419cd9d9930abc4b304802c379be57e",
|
"reference": "6b0d59040715f072982bfc92fe71414b44d45a0c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -2352,9 +2352,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/livewire/flux/issues",
|
"issues": "https://github.com/livewire/flux/issues",
|
||||||
"source": "https://github.com/livewire/flux/tree/v2.0.3"
|
"source": "https://github.com/livewire/flux/tree/v2.0.4"
|
||||||
},
|
},
|
||||||
"time": "2025-02-26T00:29:58+00:00"
|
"time": "2025-02-28T16:35:28+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/livewire",
|
"name": "livewire/livewire",
|
||||||
|
|
@ -3618,16 +3618,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ramsey/collection",
|
"name": "ramsey/collection",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/ramsey/collection.git",
|
"url": "https://github.com/ramsey/collection.git",
|
||||||
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
|
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
|
"url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
|
||||||
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
|
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -3635,25 +3635,22 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"captainhook/plugin-composer": "^5.3",
|
"captainhook/plugin-composer": "^5.3",
|
||||||
"ergebnis/composer-normalize": "^2.28.3",
|
"ergebnis/composer-normalize": "^2.45",
|
||||||
"fakerphp/faker": "^1.21",
|
"fakerphp/faker": "^1.24",
|
||||||
"hamcrest/hamcrest-php": "^2.0",
|
"hamcrest/hamcrest-php": "^2.0",
|
||||||
"jangregor/phpstan-prophecy": "^1.0",
|
"jangregor/phpstan-prophecy": "^2.1",
|
||||||
"mockery/mockery": "^1.5",
|
"mockery/mockery": "^1.6",
|
||||||
"php-parallel-lint/php-console-highlighter": "^1.0",
|
"php-parallel-lint/php-console-highlighter": "^1.0",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
"php-parallel-lint/php-parallel-lint": "^1.4",
|
||||||
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
|
"phpspec/prophecy-phpunit": "^2.3",
|
||||||
"phpspec/prophecy-phpunit": "^2.0",
|
"phpstan/extension-installer": "^1.4",
|
||||||
"phpstan/extension-installer": "^1.2",
|
"phpstan/phpstan": "^2.1",
|
||||||
"phpstan/phpstan": "^1.9",
|
"phpstan/phpstan-mockery": "^2.0",
|
||||||
"phpstan/phpstan-mockery": "^1.1",
|
"phpstan/phpstan-phpunit": "^2.0",
|
||||||
"phpstan/phpstan-phpunit": "^1.3",
|
"phpunit/phpunit": "^10.5",
|
||||||
"phpunit/phpunit": "^9.5",
|
"ramsey/coding-standard": "^2.3",
|
||||||
"psalm/plugin-mockery": "^1.1",
|
"ramsey/conventional-commits": "^1.6",
|
||||||
"psalm/plugin-phpunit": "^0.18.4",
|
"roave/security-advisories": "dev-latest"
|
||||||
"ramsey/coding-standard": "^2.0.3",
|
|
||||||
"ramsey/conventional-commits": "^1.3",
|
|
||||||
"vimeo/psalm": "^5.4"
|
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
|
@ -3691,19 +3688,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/ramsey/collection/issues",
|
"issues": "https://github.com/ramsey/collection/issues",
|
||||||
"source": "https://github.com/ramsey/collection/tree/2.0.0"
|
"source": "https://github.com/ramsey/collection/tree/2.1.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"time": "2025-03-02T04:48:29+00:00"
|
||||||
{
|
|
||||||
"url": "https://github.com/ramsey",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2022-12-31T21:50:55+00:00"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ramsey/uuid",
|
"name": "ramsey/uuid",
|
||||||
|
|
@ -3925,6 +3912,68 @@
|
||||||
],
|
],
|
||||||
"time": "2025-02-06T14:58:20+00:00"
|
"time": "2025-02-06T14:58:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/pest-expectations",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/pest-expectations.git",
|
||||||
|
"reference": "e7e7be733f315157da97a44988099374edeffc23"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/pest-expectations/zipball/e7e7be733f315157da97a44988099374edeffc23",
|
||||||
|
"reference": "e7e7be733f315157da97a44988099374edeffc23",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/contracts": "^9.47|^10.0",
|
||||||
|
"laravel/pint": "^1.2",
|
||||||
|
"pestphp/pest": "^1.20|^2.0",
|
||||||
|
"spatie/ray": "^1.28"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/PestExpectations.php",
|
||||||
|
"src/Helpers.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\PestExpectations\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A collection of handy custom Pest customisations",
|
||||||
|
"homepage": "https://github.com/spatie/pest-expectations",
|
||||||
|
"keywords": [
|
||||||
|
"pest-expectations",
|
||||||
|
"spatie"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/pest-expectations/issues",
|
||||||
|
"source": "https://github.com/spatie/pest-expectations/tree/1.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-04-05T17:19:05+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/temporary-directory",
|
"name": "spatie/temporary-directory",
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ return [
|
||||||
'trmnl' => [
|
'trmnl' => [
|
||||||
'proxy_base_url' => env('TRMNL_PROXY_BASE_URL', 'https://trmnl.app'),
|
'proxy_base_url' => env('TRMNL_PROXY_BASE_URL', 'https://trmnl.app'),
|
||||||
'proxy_refresh_minutes' => env('TRMNL_PROXY_REFRESH_MINUTES', 15),
|
'proxy_refresh_minutes' => env('TRMNL_PROXY_REFRESH_MINUTES', 15),
|
||||||
|
'override_orig_icon' => env('TRMNL_OVERRIDE_ORIG_ICON', false),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,17 @@ class DeviceFactory extends Factory
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => $this->faker->firstName().' TRMNL',
|
'name' => $this->faker->firstName().'\'s TRMNL',
|
||||||
'mac_address' => $this->faker->macAddress(),
|
'mac_address' => $this->faker->macAddress(),
|
||||||
'default_refresh_interval' => '900',
|
'default_refresh_interval' => '900',
|
||||||
'friendly_id' => Str::random(6),
|
'friendly_id' => Str::random(6),
|
||||||
'api_key' => 'tD-'.Str::random(19),
|
'api_key' => 'tD-'.Str::random(19),
|
||||||
'user_id' => 1,
|
'user_id' => 1,
|
||||||
|
'last_battery_voltage' => $this->faker->randomFloat(2, 3.0, 4.2),
|
||||||
|
'last_rssi_level' => $this->faker->numberBetween(-100, 0),
|
||||||
|
'last_firmware_version' => '1.6.0',
|
||||||
|
'proxy_cloud' => $this->faker->boolean(),
|
||||||
|
'last_log_request' => ['status' => 'success', 'timestamp' => Carbon::now()->toDateTimeString()],
|
||||||
'created_at' => Carbon::now(),
|
'created_at' => Carbon::now(),
|
||||||
'updated_at' => Carbon::now(),
|
'updated_at' => Carbon::now(),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class UserFactory extends Factory
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
'password' => static::$password ??= Hash::make('password'),
|
'password' => static::$password ??= Hash::make('password'),
|
||||||
'remember_token' => Str::random(10),
|
'remember_token' => Str::random(10),
|
||||||
|
'assign_new_devices' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -13,12 +15,14 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
if (app()->isLocal()) {
|
||||||
|
User::factory()->create([
|
||||||
|
'name' => 'Test User',
|
||||||
|
'email' => 'admin@example.com',
|
||||||
|
'password' => bcrypt('admin@example.com'),
|
||||||
|
]);
|
||||||
|
|
||||||
User::factory()->create([
|
// Device::factory(5)->create();
|
||||||
'name' => 'Test User',
|
}
|
||||||
'email' => 'admin@example.com',
|
|
||||||
'password' => bcrypt('admin@example.com'),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
340
package-lock.json
generated
|
|
@ -465,9 +465,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
|
||||||
"integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
|
"integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -478,9 +478,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz",
|
||||||
"integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
|
"integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -491,9 +491,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
|
||||||
"integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
|
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -504,9 +504,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
|
||||||
"integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
|
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -517,9 +517,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz",
|
||||||
"integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
|
"integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -530,9 +530,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz",
|
||||||
"integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
|
"integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -543,9 +543,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz",
|
||||||
"integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
|
"integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -556,9 +556,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz",
|
||||||
"integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
|
"integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -569,9 +569,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
|
||||||
"integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
|
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -582,9 +582,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
|
||||||
"integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
|
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -595,9 +595,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz",
|
||||||
"integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
|
"integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -608,9 +608,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz",
|
||||||
"integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
|
"integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -621,9 +621,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz",
|
||||||
"integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
|
"integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -634,9 +634,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz",
|
||||||
"integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
|
"integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -660,9 +660,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
|
||||||
"integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
|
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -673,9 +673,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
|
||||||
"integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
|
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -686,9 +686,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz",
|
||||||
"integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
|
"integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -699,9 +699,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
|
||||||
"integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
|
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -712,42 +712,42 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.9.tgz",
|
||||||
"integrity": "sha512-FKArQpbrbwv08TNT0k7ejYXpF+R8knZFAatNc0acOxbgeqLzwb86r+P3LGOjIeI3Idqe9CVkZrh4GlsJLJKkkw==",
|
"integrity": "sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"enhanced-resolve": "^5.18.1",
|
"enhanced-resolve": "^5.18.1",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"tailwindcss": "4.0.8"
|
"tailwindcss": "4.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide": {
|
"node_modules/@tailwindcss/oxide": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.9.tgz",
|
||||||
"integrity": "sha512-KfMcuAu/Iw+DcV1e8twrFyr2yN8/ZDC/odIGta4wuuJOGkrkHZbvJvRNIbQNhGh7erZTYV6Ie0IeD6WC9Y8Hcw==",
|
"integrity": "sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tailwindcss/oxide-android-arm64": "4.0.8",
|
"@tailwindcss/oxide-android-arm64": "4.0.9",
|
||||||
"@tailwindcss/oxide-darwin-arm64": "4.0.8",
|
"@tailwindcss/oxide-darwin-arm64": "4.0.9",
|
||||||
"@tailwindcss/oxide-darwin-x64": "4.0.8",
|
"@tailwindcss/oxide-darwin-x64": "4.0.9",
|
||||||
"@tailwindcss/oxide-freebsd-x64": "4.0.8",
|
"@tailwindcss/oxide-freebsd-x64": "4.0.9",
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.8",
|
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.9",
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.0.8",
|
"@tailwindcss/oxide-linux-arm64-gnu": "4.0.9",
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": "4.0.8",
|
"@tailwindcss/oxide-linux-arm64-musl": "4.0.9",
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": "4.0.8",
|
"@tailwindcss/oxide-linux-x64-gnu": "4.0.9",
|
||||||
"@tailwindcss/oxide-linux-x64-musl": "4.0.8",
|
"@tailwindcss/oxide-linux-x64-musl": "4.0.9",
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.0.8",
|
"@tailwindcss/oxide-win32-arm64-msvc": "4.0.9",
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": "4.0.8"
|
"@tailwindcss/oxide-win32-x64-msvc": "4.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.9.tgz",
|
||||||
"integrity": "sha512-We7K79+Sm4mwJHk26Yzu/GAj7C7myemm7PeXvpgMxyxO70SSFSL3uCcqFbz9JA5M5UPkrl7N9fkBe/Y0iazqpA==",
|
"integrity": "sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -761,9 +761,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.9.tgz",
|
||||||
"integrity": "sha512-Lv9Isi2EwkCTG1sRHNDi0uRNN1UGFdEThUAGFrydRmQZnraGLMjN8gahzg2FFnOizDl7LB2TykLUuiw833DSNg==",
|
"integrity": "sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -777,9 +777,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.9.tgz",
|
||||||
"integrity": "sha512-fWfywfYIlSWtKoqWTjukTHLWV3ARaBRjXCC2Eo0l6KVpaqGY4c2y8snUjp1xpxUtpqwMvCvFWFaleMoz1Vhzlw==",
|
"integrity": "sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -793,9 +793,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.9.tgz",
|
||||||
"integrity": "sha512-SO+dyvjJV9G94bnmq2288Ke0BIdvrbSbvtPLaQdqjqHR83v5L2fWADyFO+1oecHo9Owsk8MxcXh1agGVPIKIqw==",
|
"integrity": "sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -809,9 +809,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.9.tgz",
|
||||||
"integrity": "sha512-ZSHggWiEblQNV69V0qUK5vuAtHP+I+S2eGrKGJ5lPgwgJeAd6GjLsVBN+Mqn2SPVfYM3BOpS9jX/zVg9RWQVDQ==",
|
"integrity": "sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -825,9 +825,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.9.tgz",
|
||||||
"integrity": "sha512-xWpr6M0OZLDNsr7+bQz+3X7zcnDJZJ1N9gtBWCtfhkEtDjjxYEp+Lr5L5nc/yXlL4MyCHnn0uonGVXy3fhxaVA==",
|
"integrity": "sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -841,9 +841,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.9.tgz",
|
||||||
"integrity": "sha512-5tz2IL7LN58ssGEq7h/staD7pu/izF/KeMWdlJ86WDe2Ah46LF3ET6ZGKTr5eZMrnEA0M9cVFuSPprKRHNgjeg==",
|
"integrity": "sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -857,9 +857,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.9.tgz",
|
||||||
"integrity": "sha512-KSzMkhyrxAQyY2o194NKVKU9j/c+NFSoMvnHWFaNHKi3P1lb+Vq1UC19tLHrmxSkKapcMMu69D7+G1+FVGNDXQ==",
|
"integrity": "sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -873,9 +873,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.9.tgz",
|
||||||
"integrity": "sha512-yFYKG5UtHTRimjtqxUWXBgI4Tc6NJe3USjRIVdlTczpLRxq/SFwgzGl5JbatCxgSRDPBFwRrNPxq+ukfQFGdrw==",
|
"integrity": "sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -889,9 +889,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.9.tgz",
|
||||||
"integrity": "sha512-tndGujmCSba85cRCnQzXgpA2jx5gXimyspsUYae5jlPyLRG0RjXbDshFKOheVXU4TLflo7FSG8EHCBJ0EHTKdQ==",
|
"integrity": "sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -905,9 +905,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.9.tgz",
|
||||||
"integrity": "sha512-T77jroAc0p4EHVVgTUiNeFn6Nj3jtD3IeNId2X+0k+N1XxfNipy81BEkYErpKLiOkNhpNFjPee8/ZVas29b2OQ==",
|
"integrity": "sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -921,15 +921,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/vite": {
|
"node_modules/@tailwindcss/vite": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.9.tgz",
|
||||||
"integrity": "sha512-+SAq44yLzYlzyrb7QTcFCdU8Xa7FOA0jp+Xby7fPMUie+MY9HhJysM7Vp+vL8qIp8ceQJfLD+FjgJuJ4lL6nyg==",
|
"integrity": "sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/node": "4.0.8",
|
"@tailwindcss/node": "4.0.9",
|
||||||
"@tailwindcss/oxide": "4.0.8",
|
"@tailwindcss/oxide": "4.0.9",
|
||||||
"lightningcss": "^1.29.1",
|
"lightningcss": "^1.29.1",
|
||||||
"tailwindcss": "4.0.8"
|
"tailwindcss": "4.0.9"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vite": "^5.2.0 || ^6"
|
"vite": "^5.2.0 || ^6"
|
||||||
|
|
@ -948,9 +948,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.5",
|
"version": "22.13.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
|
||||||
"integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==",
|
"integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1062,9 +1062,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.9",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
|
||||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
|
|
@ -1101,13 +1101,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bare-os": {
|
"node_modules/bare-os": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.5.1.tgz",
|
||||||
"integrity": "sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==",
|
"integrity": "sha512-LvfVNDcWLw2AnIw5f2mWUgumW3I3N/WYGiWeimhQC1Ybt71n2FjlS9GJKeCnFeg1MKZHxzIFmpFnBXDI+sBeFg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"bare": ">=1.6.0"
|
"bare": ">=1.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bare-path": {
|
"node_modules/bare-path": {
|
||||||
|
|
@ -1215,9 +1215,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001700",
|
"version": "1.0.30001702",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz",
|
||||||
"integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
|
"integrity": "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -1263,9 +1263,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chromium-bidi": {
|
"node_modules/chromium-bidi": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-2.1.2.tgz",
|
||||||
"integrity": "sha512-8VmyVj0ewSY4pstZV0Y3rCUUwpomam8uWgHZf1XavRxJEP4vU9/dcpNuoyB+u4AQxPo96CASXz5CHPvdH+dSeQ==",
|
"integrity": "sha512-vtRWBK2uImo5/W2oG6/cDkkHSm+2t6VHgnj+Rcwhb0pP74OoUb4GipyRX/T/y39gYQPhioP0DPShn+A7P6CHNw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
|
|
@ -1452,9 +1452,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.104",
|
"version": "1.5.112",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.104.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.112.tgz",
|
||||||
"integrity": "sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g==",
|
"integrity": "sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
|
|
@ -2531,17 +2531,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/puppeteer": {
|
"node_modules/puppeteer": {
|
||||||
"version": "24.3.0",
|
"version": "24.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.3.1.tgz",
|
||||||
"integrity": "sha512-wYEx+NnEM1T6ncHB+IsTovUgx+JlZ0pv0sRGTb8IzoTeOILvyUcdU2h34bYEQ1iG5maz1VQA5eI4kzIyAVh90A==",
|
"integrity": "sha512-k0OJ7itRwkr06owp0CP3f/PsRD7Pdw4DjoCUZvjGr+aNgS1z6n/61VajIp0uBjl+V5XAQO1v/3k9bzeZLWs9OQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "2.7.1",
|
"@puppeteer/browsers": "2.7.1",
|
||||||
"chromium-bidi": "2.0.0",
|
"chromium-bidi": "2.1.2",
|
||||||
"cosmiconfig": "^9.0.0",
|
"cosmiconfig": "^9.0.0",
|
||||||
"devtools-protocol": "0.0.1402036",
|
"devtools-protocol": "0.0.1402036",
|
||||||
"puppeteer-core": "24.3.0",
|
"puppeteer-core": "24.3.1",
|
||||||
"typed-query-selector": "^2.12.0"
|
"typed-query-selector": "^2.12.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -2552,13 +2552,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/puppeteer-core": {
|
"node_modules/puppeteer-core": {
|
||||||
"version": "24.3.0",
|
"version": "24.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.3.1.tgz",
|
||||||
"integrity": "sha512-x8kQRP/xxtiFav6wWuLzrctO0HWRpSQy+JjaHbqIl+d5U2lmRh2pY9vh5AzDFN0EtOXW2pzngi9RrryY1vZGig==",
|
"integrity": "sha512-585ccfcTav4KmlSmYbwwOSeC8VdutQHn2Fuk0id/y/9OoeO7Gg5PK1aUGdZjEmos0TAq+pCpChqFurFbpNd3wA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "2.7.1",
|
"@puppeteer/browsers": "2.7.1",
|
||||||
"chromium-bidi": "2.0.0",
|
"chromium-bidi": "2.1.2",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"devtools-protocol": "0.0.1402036",
|
"devtools-protocol": "0.0.1402036",
|
||||||
"typed-query-selector": "^2.12.0",
|
"typed-query-selector": "^2.12.0",
|
||||||
|
|
@ -2587,9 +2587,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz",
|
||||||
"integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
|
"integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.6"
|
"@types/estree": "1.0.6"
|
||||||
|
|
@ -2602,32 +2602,32 @@
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.34.8",
|
"@rollup/rollup-android-arm-eabi": "4.34.9",
|
||||||
"@rollup/rollup-android-arm64": "4.34.8",
|
"@rollup/rollup-android-arm64": "4.34.9",
|
||||||
"@rollup/rollup-darwin-arm64": "4.34.8",
|
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||||
"@rollup/rollup-darwin-x64": "4.34.8",
|
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.34.8",
|
"@rollup/rollup-freebsd-arm64": "4.34.9",
|
||||||
"@rollup/rollup-freebsd-x64": "4.34.8",
|
"@rollup/rollup-freebsd-x64": "4.34.9",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.34.9",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.8",
|
"@rollup/rollup-linux-arm-musleabihf": "4.34.9",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.34.8",
|
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.34.8",
|
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
|
"@rollup/rollup-linux-loongarch64-gnu": "4.34.9",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.9",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.8",
|
"@rollup/rollup-linux-riscv64-gnu": "4.34.9",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.34.8",
|
"@rollup/rollup-linux-s390x-gnu": "4.34.9",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.34.8",
|
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.34.8",
|
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.34.8",
|
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.34.8",
|
"@rollup/rollup-win32-ia32-msvc": "4.34.9",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.34.8",
|
"@rollup/rollup-win32-x64-msvc": "4.34.9",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.34.8",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
|
||||||
"integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
|
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2788,9 +2788,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.9.tgz",
|
||||||
"integrity": "sha512-Me7N5CKR+D2A1xdWA5t5+kjjT7bwnxZOE6/yDI/ixJdJokszsn2n++mdU5yJwrsTpqFX2B9ZNMBJDwcqk9C9lw==",
|
"integrity": "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
|
|
@ -2865,9 +2865,9 @@
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||||
"integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
|
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,33 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" {{ $attributes }}>
|
@if(config('services.trmnl.override_orig_icon'))
|
||||||
<g clip-path="url(#clip0_870_1047)">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" {{ $attributes }}>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.02194 2.11328L15.8863 5.07445L14.4259 8.98481L6.56152 6.02364L8.02194 2.11328Z" fill="currentColor"/>
|
<g clip-path="url(#clip0_870_1047)">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.6315 1.14647L23.2291 9.16642L19.2738 10.458L16.6761 2.43807L20.6315 1.14647Z" fill="currentColor"/>
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.2441 10.4452L24.619 17.4848L21.1471 15.185L25.7723 8.14549L29.2441 10.4452Z" fill="currentColor"/>
|
d="M8.02194 2.11328L15.8863 5.07445L14.4259 8.98481L6.56152 6.02364L8.02194 2.11328Z"
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.3739 23.0034L19.0088 23.7616L18.6349 19.6023L27 18.8441L27.3739 23.0034Z" fill="currentColor"/>
|
fill="currentColor"/>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4296 29.3638L10.6236 23.2697L13.6292 20.3828L19.4351 26.4769L16.4296 29.3638Z" fill="currentColor"/>
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.65232 24.7423L5.77753 16.3849L9.89932 16.9443L8.77411 25.3017L4.65232 24.7423Z" fill="currentColor"/>
|
d="M20.6315 1.14647L23.2291 9.16642L19.2738 10.458L16.6761 2.43807L20.6315 1.14647Z"
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.910558 12.6074L8.11961 8.28L10.2539 11.8645L3.04481 16.192L0.910558 12.6074Z" fill="currentColor"/>
|
fill="currentColor"/>
|
||||||
</g>
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
<defs>
|
d="M29.2441 10.4452L24.619 17.4848L21.1471 15.185L25.7723 8.14549L29.2441 10.4452Z"
|
||||||
<clipPath id="clip0_870_1047">
|
fill="currentColor"/>
|
||||||
<rect width="30" height="30" fill="currentColor"/>
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
</clipPath>
|
d="M27.3739 23.0034L19.0088 23.7616L18.6349 19.6023L27 18.8441L27.3739 23.0034Z" fill="currentColor"/>
|
||||||
</defs>
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
</svg>
|
d="M16.4296 29.3638L10.6236 23.2697L13.6292 20.3828L19.4351 26.4769L16.4296 29.3638Z"
|
||||||
|
fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M4.65232 24.7423L5.77753 16.3849L9.89932 16.9443L8.77411 25.3017L4.65232 24.7423Z"
|
||||||
|
fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M0.910558 12.6074L8.11961 8.28L10.2539 11.8645L3.04481 16.192L0.910558 12.6074Z"
|
||||||
|
fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_870_1047">
|
||||||
|
<rect width="30" height="30" fill="currentColor"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
@else
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trello"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><rect width="3" height="9" x="7" y="7"/><rect width="3" height="5" x="14" y="7"/></svg>
|
||||||
|
@endif
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.1 KiB |
|
|
@ -11,42 +11,44 @@ new class extends Component {
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex w-full max-w-3xl flex-col gap-6">
|
<div class="bg-muted flex flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||||
@if($devices->isEmpty())
|
<div class="flex w-full max-w-3xl flex-col gap-6">
|
||||||
<div class="flex flex-col gap-6">
|
@if($devices->isEmpty())
|
||||||
<div
|
<div class="flex flex-col gap-6">
|
||||||
class="rounded-xl border bg-white dark:bg-stone-950 dark:border-stone-800 text-stone-800 shadow-xs">
|
<div
|
||||||
<div class="px-10 py-8">
|
class="rounded-xl border bg-white dark:bg-stone-950 dark:border-stone-800 text-stone-800 shadow-xs">
|
||||||
<h1 class="text-xl font-medium dark:text-zinc-200">Add your first device</h1>
|
<div class="px-10 py-8">
|
||||||
<flux:button href="{{ route('devices') }}" class="mt-4" icon="plus-circle" variant="primary"
|
<h1 class="text-xl font-medium dark:text-zinc-200">Add your first device</h1>
|
||||||
class="w-full mt-4">Add Device
|
<flux:button href="{{ route('devices') }}" class="mt-4" icon="plus-circle" variant="primary"
|
||||||
</flux:button>
|
class="w-full mt-4">Add Device
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endif
|
||||||
@endif
|
|
||||||
|
|
||||||
@foreach($devices as $device)
|
@foreach($devices as $device)
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<div
|
<div
|
||||||
class="rounded-xl border bg-white dark:bg-stone-950 dark:border-stone-800 text-stone-800 shadow-xs">
|
class="rounded-xl border bg-white dark:bg-stone-950 dark:border-stone-800 text-stone-800 shadow-xs">
|
||||||
<div class="px-10 py-8">
|
<div class="px-10 py-8">
|
||||||
@php
|
@php
|
||||||
$current_image_uuid =$device->current_screen_image;
|
$current_image_uuid =$device->current_screen_image;
|
||||||
file_exists('storage/images/generated/' . $current_image_uuid . '.png') ? $file_extension = 'png' : $file_extension = 'bmp';
|
file_exists('storage/images/generated/' . $current_image_uuid . '.png') ? $file_extension = 'png' : $file_extension = 'bmp';
|
||||||
$current_image_path = 'storage/images/generated/' . $current_image_uuid . '.' . $file_extension;
|
$current_image_path = 'storage/images/generated/' . $current_image_uuid . '.' . $file_extension;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<h1 class="text-xl font-medium dark:text-zinc-200">{{ $device->name }}</h1>
|
<h1 class="text-xl font-medium dark:text-zinc-200">{{ $device->name }}</h1>
|
||||||
<p class="text-sm dark:text-zinc-400">{{$device->mac_address}}</p>
|
<p class="text-sm dark:text-zinc-400">{{$device->mac_address}}</p>
|
||||||
@if($current_image_uuid)
|
@if($current_image_uuid)
|
||||||
<flux:separator class="mt-2 mb-4"/>
|
<flux:separator class="mt-2 mb-4"/>
|
||||||
<img src="{{ asset($current_image_path) }}" alt="Current Image"/>
|
<img src="{{ asset($current_image_path) }}" alt="Current Image"/>
|
||||||
@endif
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endforeach
|
||||||
@endforeach
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- @php--}}
|
{{-- @php--}}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,4 @@
|
||||||
|
|
||||||
use App\Jobs\FetchProxyCloudResponses;
|
use App\Jobs\FetchProxyCloudResponses;
|
||||||
|
|
||||||
// Artisan::command('inspire', function () {
|
Schedule::job(FetchProxyCloudResponses::class, [])->cron(sprintf('*/%s * * * *', intval(config('services.trmnl.proxy_refresh_minutes', 15))));
|
||||||
// $this->comment(Inspiring::quote());
|
|
||||||
// })->purpose('Display an inspiring quote')->hourly();
|
|
||||||
|
|
||||||
Schedule::job(new FetchProxyCloudResponses)->everyFifteenMinutes();
|
|
||||||
|
|
|
||||||
BIN
screenshots/README_byos-screenshot2.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
screenshots/README_byos-screenshot3.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
screenshots/README_byos-screenshot4.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
screenshots/README_byos-screenshot5.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
screenshots/README_byos-screenshot6.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
12
screenshots/SCREENSHOTS.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
## Sceenshots
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
158
tests/Feature/Api/DeviceEndpointsTest.php
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Storage::fake('public');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('device can fetch display data with valid credentials', function () {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'mac_address' => '00:11:22:33:44:55',
|
||||||
|
'api_key' => 'test-api-key',
|
||||||
|
'current_screen_image' => 'test-image',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'id' => $device->mac_address,
|
||||||
|
'access-token' => $device->api_key,
|
||||||
|
'rssi' => -70,
|
||||||
|
'battery_voltage' => 3.8,
|
||||||
|
'fw-version' => '1.0.0',
|
||||||
|
])->get('/api/display');
|
||||||
|
|
||||||
|
$response->assertOk()
|
||||||
|
->assertJson([
|
||||||
|
'status' => '0',
|
||||||
|
'filename' => 'test-image.bmp',
|
||||||
|
'refresh_rate' => 900,
|
||||||
|
'reset_firmware' => false,
|
||||||
|
'update_firmware' => false,
|
||||||
|
'firmware_url' => null,
|
||||||
|
'special_function' => 'sleep',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($device->fresh())
|
||||||
|
->last_rssi_level->toBe(-70)
|
||||||
|
->last_battery_voltage->toBe(3.8)
|
||||||
|
->last_firmware_version->toBe('1.0.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('new device is auto-assigned to user with auto-assign enabled', function () {
|
||||||
|
$user = User::factory()->create(['assign_new_devices' => true]);
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'id' => '00:11:22:33:44:55',
|
||||||
|
'access-token' => 'new-device-key',
|
||||||
|
'rssi' => -70,
|
||||||
|
'battery_voltage' => 3.8,
|
||||||
|
'fw-version' => '1.0.0',
|
||||||
|
])->get('/api/display');
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
|
||||||
|
$device = Device::where('mac_address', '00:11:22:33:44:55')->first();
|
||||||
|
expect($device)
|
||||||
|
->not->toBeNull()
|
||||||
|
->user_id->toBe($user->id)
|
||||||
|
->api_key->toBe('new-device-key');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('device setup endpoint returns correct data', function () {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'mac_address' => '00:11:22:33:44:55',
|
||||||
|
'api_key' => 'test-api-key',
|
||||||
|
'friendly_id' => 'test-device',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'id' => $device->mac_address,
|
||||||
|
])->get('/api/setup');
|
||||||
|
|
||||||
|
$response->assertOk()
|
||||||
|
->assertJson([
|
||||||
|
'api_key' => 'test-api-key',
|
||||||
|
'friendly_id' => 'test-device',
|
||||||
|
'message' => 'Welcome to TRMNL BYOS',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('device can submit logs', function () {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'mac_address' => '00:11:22:33:44:55',
|
||||||
|
'api_key' => 'test-api-key',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$logData = [
|
||||||
|
'log' => [
|
||||||
|
'logs_array' => [
|
||||||
|
['message' => 'Test log message', 'level' => 'info'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'id' => $device->mac_address,
|
||||||
|
'access-token' => $device->api_key,
|
||||||
|
])->postJson('/api/log', $logData);
|
||||||
|
|
||||||
|
$response->assertOk()
|
||||||
|
->assertJson(['status' => '0']);
|
||||||
|
|
||||||
|
expect($device->fresh()->last_log_request)
|
||||||
|
->toBe($logData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// test('authenticated user can update device display', function () {
|
||||||
|
// $user = User::factory()->create();
|
||||||
|
// $device = Device::factory()->create(['user_id' => $user->id]);
|
||||||
|
//
|
||||||
|
// Sanctum::actingAs($user, ['update-screen']);
|
||||||
|
//
|
||||||
|
// $response = $this->postJson('/api/display/update', [
|
||||||
|
// 'device_id' => $device->id,
|
||||||
|
// 'markup' => '<div>Test markup</div>'
|
||||||
|
// ]);
|
||||||
|
//
|
||||||
|
// $response->assertOk();
|
||||||
|
// });
|
||||||
|
|
||||||
|
test('user cannot update display for devices they do not own', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$otherUser = User::factory()->create();
|
||||||
|
$device = Device::factory()->create(['user_id' => $otherUser->id]);
|
||||||
|
|
||||||
|
Sanctum::actingAs($user, ['update-screen']);
|
||||||
|
|
||||||
|
$response = $this->postJson('/api/display/update', [
|
||||||
|
'device_id' => $device->id,
|
||||||
|
'markup' => '<div>Test markup</div>',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid device credentials return error', function () {
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'id' => 'invalid-mac',
|
||||||
|
'access-token' => 'invalid-token',
|
||||||
|
])->get('/api/display');
|
||||||
|
|
||||||
|
$response->assertNotFound()
|
||||||
|
->assertJson(['message' => 'MAC Address not registered or invalid access token']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('log endpoint requires valid device credentials', function () {
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'id' => 'invalid-mac',
|
||||||
|
'access-token' => 'invalid-token',
|
||||||
|
])->postJson('/api/log', ['log' => []]);
|
||||||
|
|
||||||
|
$response->assertNotFound()
|
||||||
|
->assertJson(['message' => 'Device not found or invalid access token']);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\FetchProxyCloudResponses;
|
||||||
|
use Illuminate\Support\Facades\Bus;
|
||||||
|
|
||||||
|
test('it dispatches fetch proxy cloud responses job', function () {
|
||||||
|
// Prevent the job from actually running
|
||||||
|
Bus::fake();
|
||||||
|
|
||||||
|
// Run the command
|
||||||
|
$this->artisan('trmnl:cloud:proxy')->assertSuccessful();
|
||||||
|
|
||||||
|
// Assert that the job was dispatched
|
||||||
|
Bus::assertDispatched(FetchProxyCloudResponses::class);
|
||||||
|
});
|
||||||
13
tests/Feature/Console/ScreenGeneratorCommandTest.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\GenerateScreenJob;
|
||||||
|
use Illuminate\Support\Facades\Bus;
|
||||||
|
|
||||||
|
test('it generates screen with default parameters', function () {
|
||||||
|
Bus::fake();
|
||||||
|
|
||||||
|
$this->artisan('trmnl:screen:generate')
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
Bus::assertDispatched(GenerateScreenJob::class);
|
||||||
|
});
|
||||||
76
tests/Feature/Devices/DeviceTest.php
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
|
||||||
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
test('device can be created with basic attributes', function () {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'name' => 'Test Device',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($device)->toBeInstanceOf(Device::class)
|
||||||
|
->and($device->name)->toBe('Test Device');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('battery percentage is calculated correctly', function () {
|
||||||
|
$cases = [
|
||||||
|
['voltage' => 3.0, 'expected' => 0], // Min voltage
|
||||||
|
['voltage' => 4.2, 'expected' => 100], // Max voltage
|
||||||
|
['voltage' => 2.9, 'expected' => 0], // Below min
|
||||||
|
['voltage' => 4.3, 'expected' => 100], // Above max
|
||||||
|
['voltage' => 3.6, 'expected' => 50.0], // Middle voltage
|
||||||
|
['voltage' => 3.3, 'expected' => 25.0], // Quarter voltage
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($cases as $case) {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'last_battery_voltage' => $case['voltage'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($device->battery_percent)->toBe($case['expected'])
|
||||||
|
->and($device->last_battery_voltage)->toBe($case['voltage']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('wifi strength is determined correctly', function () {
|
||||||
|
$cases = [
|
||||||
|
['rssi' => 0, 'expected' => 0], // No signal
|
||||||
|
['rssi' => -90, 'expected' => 1], // Weak signal
|
||||||
|
['rssi' => -70, 'expected' => 2], // Moderate signal
|
||||||
|
['rssi' => -50, 'expected' => 3], // Strong signal
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($cases as $case) {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'last_rssi_level' => $case['rssi'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($device->wifi_strengh)->toBe($case['expected'])
|
||||||
|
->and($device->last_rssi_level)->toBe($case['rssi']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('proxy cloud attribute is properly cast to boolean', function () {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'proxy_cloud' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($device->proxy_cloud)->toBeTrue();
|
||||||
|
|
||||||
|
$device->update(['proxy_cloud' => false]);
|
||||||
|
expect($device->proxy_cloud)->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('last log request is properly cast to json', function () {
|
||||||
|
$logData = ['status' => 'success', 'timestamp' => '2024-03-04 12:00:00'];
|
||||||
|
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'last_log_request' => $logData,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($device->last_log_request)
|
||||||
|
->toBeArray()
|
||||||
|
->toHaveKey('status')
|
||||||
|
->toHaveKey('timestamp');
|
||||||
|
});
|
||||||
106
tests/Feature/Devices/ManageTest.php
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Models\User;
|
||||||
|
use Livewire\Volt\Volt;
|
||||||
|
|
||||||
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
test('device management page can be rendered', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)
|
||||||
|
->get('/devices');
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user can create a new device', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$deviceData = [
|
||||||
|
'name' => 'Test Device',
|
||||||
|
'mac_address' => '00:11:22:33:44:55',
|
||||||
|
'api_key' => 'test-api-key',
|
||||||
|
'default_refresh_interval' => 900,
|
||||||
|
'friendly_id' => 'test-device-1',
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = Volt::test('devices.manage')
|
||||||
|
->set('name', $deviceData['name'])
|
||||||
|
->set('mac_address', $deviceData['mac_address'])
|
||||||
|
->set('api_key', $deviceData['api_key'])
|
||||||
|
->set('default_refresh_interval', $deviceData['default_refresh_interval'])
|
||||||
|
->set('friendly_id', $deviceData['friendly_id'])
|
||||||
|
->call('createDevice');
|
||||||
|
|
||||||
|
$response->assertHasNoErrors();
|
||||||
|
|
||||||
|
expect(Device::count())->toBe(1);
|
||||||
|
|
||||||
|
$device = Device::first();
|
||||||
|
expect($device->name)->toBe($deviceData['name']);
|
||||||
|
expect($device->mac_address)->toBe($deviceData['mac_address']);
|
||||||
|
expect($device->api_key)->toBe($deviceData['api_key']);
|
||||||
|
expect($device->default_refresh_interval)->toBe($deviceData['default_refresh_interval']);
|
||||||
|
expect($device->friendly_id)->toBe($deviceData['friendly_id']);
|
||||||
|
expect($device->user_id)->toBe($user->id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('device creation requires required fields', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$response = Volt::test('devices.manage')
|
||||||
|
->set('name', '')
|
||||||
|
->set('mac_address', '')
|
||||||
|
->set('api_key', '')
|
||||||
|
->set('default_refresh_interval', '')
|
||||||
|
->set('friendly_id', '')
|
||||||
|
->call('createDevice');
|
||||||
|
|
||||||
|
$response->assertHasErrors([
|
||||||
|
'mac_address',
|
||||||
|
'api_key',
|
||||||
|
'default_refresh_interval',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user can toggle proxy cloud for their device', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'proxy_cloud' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = Volt::test('devices.manage')
|
||||||
|
->call('toggleProxyCloud', $device);
|
||||||
|
|
||||||
|
$response->assertHasNoErrors();
|
||||||
|
expect($device->fresh()->proxy_cloud)->toBeTrue();
|
||||||
|
|
||||||
|
// Toggle back to false
|
||||||
|
$response = Volt::test('devices.manage')
|
||||||
|
->call('toggleProxyCloud', $device);
|
||||||
|
|
||||||
|
expect($device->fresh()->proxy_cloud)->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user cannot toggle proxy cloud for other users devices', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$otherUser = User::factory()->create();
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'user_id' => $otherUser->id,
|
||||||
|
'proxy_cloud' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = Volt::test('devices.manage')
|
||||||
|
->call('toggleProxyCloud', $device);
|
||||||
|
|
||||||
|
$response->assertStatus(403);
|
||||||
|
expect($device->fresh()->proxy_cloud)->toBeFalse();
|
||||||
|
});
|
||||||
140
tests/Feature/FetchProxyCloudResponsesTest.php
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\FetchProxyCloudResponses;
|
||||||
|
use App\Models\Device;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Storage::fake('public');
|
||||||
|
Storage::disk('public')->makeDirectory('/images/generated');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it fetches and processes proxy cloud responses for devices', function () {
|
||||||
|
config(['services.trmnl.proxy_base_url' => 'https://example.com']);
|
||||||
|
|
||||||
|
// Create a test device with proxy cloud enabled
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'proxy_cloud' => true,
|
||||||
|
'mac_address' => '00:11:22:33:44:55',
|
||||||
|
'api_key' => 'test-api-key',
|
||||||
|
'last_rssi_level' => -70,
|
||||||
|
'last_battery_voltage' => 3.7,
|
||||||
|
'default_refresh_interval' => 300,
|
||||||
|
'last_firmware_version' => '1.0.0',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Mock the API response
|
||||||
|
Http::fake([
|
||||||
|
config('services.trmnl.proxy_base_url').'/api/display' => Http::response([
|
||||||
|
'image_url' => 'https://example.com/test-image.bmp',
|
||||||
|
'filename' => 'test-image',
|
||||||
|
]),
|
||||||
|
'https://example.com/test-image.bmp' => Http::response('fake-image-content'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Http::withHeaders([
|
||||||
|
'id' => $device->mac_address,
|
||||||
|
'access-token' => $device->api_key,
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rssi' => $device->last_rssi_level,
|
||||||
|
'battery_voltage' => $device->last_battery_voltage,
|
||||||
|
'refresh-rate' => $device->default_refresh_interval,
|
||||||
|
'fw-version' => $device->last_firmware_version,
|
||||||
|
'accept-encoding' => 'identity;q=1,chunked;q=0.1,*;q=0',
|
||||||
|
'user-agent' => 'ESP32HTTPClient',
|
||||||
|
])->get(config('services.trmnl.proxy_base_url').'/api/display');
|
||||||
|
|
||||||
|
// Run the job
|
||||||
|
$job = new FetchProxyCloudResponses;
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
// Assert HTTP requests were made with correct headers
|
||||||
|
Http::assertSent(function ($request) use ($device) {
|
||||||
|
return $request->hasHeader('id', $device->mac_address) &&
|
||||||
|
$request->hasHeader('access-token', $device->api_key) &&
|
||||||
|
$request->hasHeader('width', 800) &&
|
||||||
|
$request->hasHeader('height', 480) &&
|
||||||
|
$request->hasHeader('rssi', $device->last_rssi_level) &&
|
||||||
|
$request->hasHeader('battery_voltage', $device->last_battery_voltage) &&
|
||||||
|
$request->hasHeader('refresh-rate', $device->default_refresh_interval) &&
|
||||||
|
$request->hasHeader('fw-version', $device->last_firmware_version);
|
||||||
|
});
|
||||||
|
// Assert the device was updated
|
||||||
|
$device->refresh();
|
||||||
|
|
||||||
|
expect($device->current_screen_image)->toBe('test-image')
|
||||||
|
->and($device->proxy_cloud_response)->toBe('{"image_url":"https:\\/\\/example.com\\/test-image.bmp","filename":"test-image"}');
|
||||||
|
|
||||||
|
// Assert the image was saved
|
||||||
|
Storage::disk('public')->assertExists('images/generated/test-image.bmp');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it handles log requests when present', function () {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'proxy_cloud' => true,
|
||||||
|
'mac_address' => '00:11:22:33:44:55',
|
||||||
|
'api_key' => 'test-api-key',
|
||||||
|
'last_log_request' => ['message' => 'test log'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
config('services.trmnl.proxy_base_url').'/api/display' => Http::response([
|
||||||
|
'image_url' => 'https://example.com/test-image.bmp',
|
||||||
|
'filename' => 'test-image',
|
||||||
|
]),
|
||||||
|
'https://example.com/test-image.bmp' => Http::response('fake-image-content'),
|
||||||
|
config('services.trmnl.proxy_base_url').'/api/log' => Http::response(null, 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$job = new FetchProxyCloudResponses;
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
// Assert log request was sent
|
||||||
|
Http::assertSent(function ($request) use ($device) {
|
||||||
|
return $request->url() === config('services.trmnl.proxy_base_url').'/api/log' &&
|
||||||
|
$request->hasHeader('id', $device->mac_address) &&
|
||||||
|
$request->body() === json_encode(['message' => 'test log']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert log request was cleared
|
||||||
|
$device->refresh();
|
||||||
|
expect($device->last_log_request)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it handles API errors gracefully', function () {
|
||||||
|
$device = Device::factory()->create([
|
||||||
|
'proxy_cloud' => true,
|
||||||
|
'mac_address' => '00:11:22:33:44:55',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
config('services.trmnl.proxy_base_url').'/api/display' => Http::response(null, 500),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$job = new FetchProxyCloudResponses;
|
||||||
|
|
||||||
|
// Job should not throw exception but log error
|
||||||
|
expect(fn () => $job->handle())->not->toThrow(Exception::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it only processes proxy cloud enabled devices', function () {
|
||||||
|
Http::fake();
|
||||||
|
$enabledDevice = Device::factory()->create(['proxy_cloud' => true]);
|
||||||
|
$disabledDevice = Device::factory()->create(['proxy_cloud' => false]);
|
||||||
|
|
||||||
|
$job = new FetchProxyCloudResponses;
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
// Assert request was only made for enabled device
|
||||||
|
Http::assertSent(function ($request) use ($enabledDevice) {
|
||||||
|
return $request->hasHeader('id', $enabledDevice->mac_address);
|
||||||
|
});
|
||||||
|
|
||||||
|
Http::assertNotSent(function ($request) use ($disabledDevice) {
|
||||||
|
return $request->hasHeader('id', $disabledDevice->mac_address);
|
||||||
|
});
|
||||||
|
});
|
||||||
59
tests/Feature/GenerateScreenJobTest.php
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\GenerateScreenJob;
|
||||||
|
use App\Models\Device;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Storage::fake('public');
|
||||||
|
Storage::disk('public')->makeDirectory('/images/generated');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it generates screen images and updates device', function () {
|
||||||
|
$device = Device::factory()->create();
|
||||||
|
$job = new GenerateScreenJob($device->id, view('trmnl')->render());
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
// Assert the device was updated with a new image UUID
|
||||||
|
$device->refresh();
|
||||||
|
expect($device->current_screen_image)->not->toBeNull();
|
||||||
|
|
||||||
|
// Assert both PNG and BMP files were created
|
||||||
|
$uuid = $device->current_screen_image;
|
||||||
|
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||||
|
Storage::disk('public')->assertExists("/images/generated/{$uuid}.bmp");
|
||||||
|
})->skipOnGitHubActions();
|
||||||
|
|
||||||
|
test('it cleans up unused images', function () {
|
||||||
|
// Create some test devices with images
|
||||||
|
$activeDevice = Device::factory()->create([
|
||||||
|
'current_screen_image' => 'uuid-to-be-replaced',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create some test files
|
||||||
|
Storage::disk('public')->put('/images/generated/uuid-to-be-replaced.png', 'test');
|
||||||
|
Storage::disk('public')->put('/images/generated/uuid-to-be-replaced.bmp', 'test');
|
||||||
|
Storage::disk('public')->put('/images/generated/inactive-uuid.png', 'test');
|
||||||
|
Storage::disk('public')->put('/images/generated/inactive-uuid.bmp', 'test');
|
||||||
|
|
||||||
|
// Run a job which will trigger cleanup
|
||||||
|
$job = new GenerateScreenJob($activeDevice->id, '<div>Test</div>');
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
Storage::disk('public')->assertMissing('/images/generated/uuid-to-be-replaced.png');
|
||||||
|
Storage::disk('public')->assertMissing('/images/generated/uuid-to-be-replaced.bmp');
|
||||||
|
Storage::disk('public')->assertMissing('/images/generated/inactive-uuid.png');
|
||||||
|
Storage::disk('public')->assertMissing('/images/generated/inactive-uuid.bmp');
|
||||||
|
})->skipOnGitHubActions();
|
||||||
|
|
||||||
|
test('it preserves gitignore file during cleanup', function () {
|
||||||
|
Storage::disk('public')->put('/images/generated/.gitignore', '*');
|
||||||
|
|
||||||
|
$device = Device::factory()->create();
|
||||||
|
$job = new GenerateScreenJob($device->id, '<div>Test</div>');
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
||||||
|
})->skipOnGitHubActions();
|
||||||
|
|
@ -15,6 +15,7 @@ pest()->extend(Tests\TestCase::class)
|
||||||
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
|
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
|
||||||
->in('Feature');
|
->in('Feature');
|
||||||
|
|
||||||
|
registerSpatiePestHelpers();
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Expectations
|
| Expectations
|
||||||
|
|
@ -26,9 +27,9 @@ pest()->extend(Tests\TestCase::class)
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
expect()->extend('toBeOne', function () {
|
// expect()->extend('toBeOne', function () {
|
||||||
return $this->toBe(1);
|
// return $this->toBe(1);
|
||||||
});
|
// });
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -41,7 +42,7 @@ expect()->extend('toBeOne', function () {
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function something()
|
// function something()
|
||||||
{
|
// {
|
||||||
// ..
|
// // ..
|
||||||
}
|
// }
|
||||||
|
|
|
||||||