mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
initial commit
This commit is contained in:
parent
32d63c09e7
commit
01655b413e
37 changed files with 2564 additions and 283 deletions
94
README.md
94
README.md
|
|
@ -1,66 +1,70 @@
|
|||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
## Laravel Trmnl Server
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
This is a PoC of a TRMNL server, written in Laravel. Inspired by https://github.com/usetrmnl/byos_sinatra
|
||||
|
||||
## About Laravel
|
||||
⚠️ Not meant for production use.️️ ⚠️
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
### Installation
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
#### Clone the repository
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
```bash
|
||||
git clone git@github.com:bnussbau/laravel-trmnl-server.git
|
||||
```
|
||||
|
||||
## Learning Laravel
|
||||
#### Copy environment file
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
|
||||
```bash
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
|
||||
#### Install dependencies
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
```bash
|
||||
composer install
|
||||
npm i
|
||||
```
|
||||
|
||||
## Laravel Sponsors
|
||||
#### Run migrations
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
```bash
|
||||
php artisan migrate --seed
|
||||
```
|
||||
|
||||
### Premium Partners
|
||||
#### Run the server
|
||||
|
||||
- **[Vehikl](https://vehikl.com/)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[WebReinvent](https://webreinvent.com/)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
|
||||
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Jump24](https://jump24.co.uk)**
|
||||
- **[Redberry](https://redberry.international/laravel/)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
- **[byte5](https://byte5.de)**
|
||||
- **[OP.GG](https://op.gg)**
|
||||
To make your server accessible in the network, you can run the following command:
|
||||
|
||||
## Contributing
|
||||
```bash
|
||||
php artisan serve --host=0.0.0.0 --port 4567
|
||||
```
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
### Usage
|
||||
|
||||
## Code of Conduct
|
||||
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
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
#### Add your TRMNL Device
|
||||
|
||||
## Security Vulnerabilities
|
||||
http://localhost:4567/devices
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
* Add new device
|
||||
* You can grab the TRMNL Mac Address and API Key from the TRMNL Dashboard. Or debug the incoming request to `/api/setup` to determine.
|
||||
|
||||
## License
|
||||
#### 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)
|
||||
|
||||
#### Generate Screen
|
||||
|
||||
* Edit resources/views/trmnl.blade.php
|
||||
* To generate the screen, run
|
||||
|
||||
```bash
|
||||
php artisan trmnl:screen:generate
|
||||
```
|
||||
|
||||
|
||||
### License
|
||||
MIT
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
|
|
|
|||
121
app/Console/Commands/ScreenGeneratorCommand.php
Normal file
121
app/Console/Commands/ScreenGeneratorCommand.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Device;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Spatie\Browsershot\Browsershot;
|
||||
|
||||
class ScreenGeneratorCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'trmnl:screen:generate {deviceId=1}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$deviceId = $this->argument('deviceId');
|
||||
|
||||
$uuid = Uuid::uuid4()->toString();
|
||||
$pngPath = public_path('storage/images/generated/').$uuid.'.png';
|
||||
$bmpPath = public_path('storage/images/generated/').$uuid.'.bmp';
|
||||
|
||||
// Generate PNG
|
||||
try {
|
||||
Browsershot::html(view('trmnl')->render())
|
||||
->windowSize(800, 480)
|
||||
->save($pngPath);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to generate PNG: '.$e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->convertToBmpImageMagick($pngPath, $bmpPath);
|
||||
|
||||
} catch (\ImagickException $e) {
|
||||
$this->error('Failed to convert image to BMP: '.$e->getMessage());
|
||||
}
|
||||
|
||||
Device::find($deviceId)->update(['current_screen_image' => $uuid]);
|
||||
|
||||
$this->cleanupFolder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function convertToBmpImageMagick(string $pngPath, string $bmpPath): void
|
||||
{
|
||||
$imagick = new \Imagick($pngPath);
|
||||
$imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE);
|
||||
$imagick->quantizeImage(2, \Imagick::COLORSPACE_GRAY, 0, true, false);
|
||||
$imagick->setImageDepth(1);
|
||||
$imagick->stripImage();
|
||||
$imagick->setFormat('BMP3');
|
||||
$imagick->writeImage($bmpPath);
|
||||
$imagick->clear();
|
||||
}
|
||||
|
||||
// TODO retuns 8-bit BMP
|
||||
|
||||
// public function convertToBmpGD(string $pngPath, string $bmpPath): void
|
||||
// {
|
||||
// // Load the PNG image
|
||||
// $image = imagecreatefrompng($pngPath);
|
||||
//
|
||||
// // Create a new true color image with the same dimensions
|
||||
// $bwImage = imagecreatetruecolor(imagesx($image), imagesy($image));
|
||||
//
|
||||
// // Convert to black and white
|
||||
// imagefilter($image, IMG_FILTER_GRAYSCALE);
|
||||
//
|
||||
// // Copy the grayscale image to the new image
|
||||
// imagecopy($bwImage, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
|
||||
//
|
||||
// // Create a 1-bit palette
|
||||
// imagetruecolortopalette($bwImage, true, 2);
|
||||
//
|
||||
// // Save as BMP
|
||||
//
|
||||
// imagebmp($bwImage, $bmpPath, false);
|
||||
//
|
||||
// // Free up memory
|
||||
// imagedestroy($image);
|
||||
// imagedestroy($bwImage);
|
||||
// }
|
||||
public function cleanupFolder(): void
|
||||
{
|
||||
$activeImageUuids = Device::pluck('current_screen_image')->filter()->toArray();
|
||||
|
||||
$files = Storage::disk('public')->files('/images/generated/');
|
||||
foreach ($files as $file) {
|
||||
if (basename($file) === '.gitignore') {
|
||||
continue;
|
||||
}
|
||||
// Get filename without path and extension
|
||||
$fileUuid = pathinfo($file, PATHINFO_FILENAME);
|
||||
// If the UUID is not in use by any device, move it to archive
|
||||
if (! in_array($fileUuid, $activeImageUuids)) {
|
||||
Storage::disk('public')->delete($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
app/Livewire/DeviceManager.php
Normal file
59
app/Livewire/DeviceManager.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Device;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class DeviceManager extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $showDeviceForm = false;
|
||||
|
||||
public $name;
|
||||
|
||||
public $mac_address;
|
||||
|
||||
public $api_key;
|
||||
|
||||
public $default_refresh_interval = 900;
|
||||
|
||||
public $friendly_id;
|
||||
|
||||
protected $rules = [
|
||||
'mac_address' => 'required',
|
||||
'api_key' => 'required',
|
||||
'default_refresh_interval' => 'required|integer',
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.device-manager', [
|
||||
'devices' => auth()->user()->devices()->paginate(10),
|
||||
]);
|
||||
}
|
||||
|
||||
public function createDevice(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
Device::factory([
|
||||
'name' => $this->name,
|
||||
'mac_address' => $this->mac_address,
|
||||
'api_key' => $this->api_key,
|
||||
'default_refresh_interval' => $this->default_refresh_interval,
|
||||
'friendly_id' => $this->friendly_id,
|
||||
'user_id' => auth()->id(),
|
||||
])->create();
|
||||
|
||||
$this->reset();
|
||||
session()->flash('message', 'Device created successfully.');
|
||||
}
|
||||
|
||||
public function toggleDeviceForm()
|
||||
{
|
||||
$this->showDeviceForm = ! $this->showDeviceForm;
|
||||
}
|
||||
}
|
||||
13
app/Models/Device.php
Normal file
13
app/Models/Device.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Device extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
}
|
||||
|
|
@ -4,13 +4,15 @@ namespace App\Models;
|
|||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
|
@ -45,4 +47,9 @@ class User extends Authenticatable
|
|||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
public function devices(): HasMany
|
||||
{
|
||||
return $this->hasMany(Device::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
app/Utils/OneBitModifier.php
Normal file
26
app/Utils/OneBitModifier.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\ModifierInterface;
|
||||
|
||||
class OneBitModifier implements ModifierInterface
|
||||
{
|
||||
public function apply(ImageInterface $image): ImageInterface
|
||||
{
|
||||
foreach ($image as $pixel) {
|
||||
// Get brightness value of pixel (0-255)
|
||||
$brightness = $pixel->brightness();
|
||||
|
||||
// Convert to Black or White based on a threshold (128)
|
||||
if ($brightness < 128) {
|
||||
$pixel->set([0, 0, 0]); // Black
|
||||
} else {
|
||||
$pixel->set([255, 255, 255]); // White
|
||||
}
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use Illuminate\Foundation\Configuration\Middleware;
|
|||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,15 @@
|
|||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"ext-imagick": "*",
|
||||
"bnussbau/laravel-trmnl": "^0.1.0",
|
||||
"intervention/image": "^3.11",
|
||||
"laravel/framework": "^11.31",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.9",
|
||||
"livewire/livewire": "^3.4",
|
||||
"livewire/volt": "^1.0"
|
||||
"livewire/volt": "^1.0",
|
||||
"spatie/browsershot": "^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
|
|
@ -21,7 +26,8 @@
|
|||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"pestphp/pest": "^3.7",
|
||||
"pestphp/pest-plugin-laravel": "^3.1"
|
||||
"pestphp/pest-plugin-laravel": "^3.1",
|
||||
"spatie/ray": "^1.41"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
|||
906
composer.lock
generated
906
composer.lock
generated
File diff suppressed because it is too large
Load diff
83
config/sanctum.php
Normal file
83
config/sanctum.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort()
|
||||
))),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array contains the authentication guards that will be checked when
|
||||
| Sanctum is trying to authenticate a request. If none of these guards
|
||||
| are able to authenticate the request, Sanctum will use the bearer
|
||||
| token that's present on an incoming request for authentication.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expiration Minutes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the number of minutes until an issued token will be
|
||||
| considered expired. This will override any values set in the token's
|
||||
| "expires_at" attribute, but first-party sessions are not affected.
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Token Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sanctum can prefix new tokens in order to take advantage of numerous
|
||||
| security scanning initiatives maintained by open source platforms
|
||||
| that notify developers if they commit tokens into repositories.
|
||||
|
|
||||
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
||||
|
|
||||
*/
|
||||
|
||||
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When authenticating your first-party SPA with Sanctum you may need to
|
||||
| customize some of the middleware Sanctum uses while processing the
|
||||
| request. You may change the middleware listed below as required.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
],
|
||||
|
||||
];
|
||||
27
database/factories/DeviceFactory.php
Normal file
27
database/factories/DeviceFactory.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Device;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DeviceFactory extends Factory
|
||||
{
|
||||
protected $model = Device::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->firstName().' TRMNL',
|
||||
'mac_address' => $this->faker->macAddress(),
|
||||
'default_refresh_interval' => '900',
|
||||
'friendly_id' => Str::random(6),
|
||||
'api_key' => 'tD-'.Str::random(19),
|
||||
'user_id' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('devices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->nullable();
|
||||
$table->string('mac_address');
|
||||
$table->integer('default_refresh_interval')->default(900);
|
||||
$table->string('friendly_id')->nullable();
|
||||
$table->string('api_key')->nullable();
|
||||
$table->integer('last_rssi_level')->nullable();
|
||||
$table->double('last_battery_voltage')->nullable();
|
||||
$table->string('last_firmware_version')->nullable();
|
||||
$table->string('current_screen_image')->nullable();
|
||||
$table->unsignedBigInteger('user_id')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('devices');
|
||||
}
|
||||
};
|
||||
BIN
example.png
Normal file
BIN
example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
918
package-lock.json
generated
918
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -14,5 +14,8 @@
|
|||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.1.0",
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"puppeteer": "^24.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
|
||||
<svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
<g clip-path="url(#clip0_870_1047)">
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.3739 23.0034L19.0088 23.7616L18.6349 19.6023L27 18.8441L27.3739 23.0034Z" fill="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_870_1047">
|
||||
<rect width="30" height="30" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 1.3 KiB |
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 dark:border-indigo-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-orange-400 dark:border-orange-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-orange-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:outline-none focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150']) }}>
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out'
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-orange-400 dark:border-orange-600 text-start text-base font-medium text-orange-700 dark:text-orange-300 bg-orange-50 dark:bg-orange-900/50 focus:outline-none focus:text-orange-800 dark:focus:text-orange-200 focus:bg-orange-100 dark:focus:bg-orange-900 focus:border-orange-700 dark:focus:border-orange-300 transition duration-150 ease-in-out'
|
||||
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 dark:focus:text-gray-200 focus:bg-gray-50 dark:focus:bg-gray-700 focus:border-gray-300 dark:focus:border-gray-600 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
@props(['disabled' => false])
|
||||
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm']) }}>
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-orange-500 dark:focus:border-orange-600 focus:ring-orange-500 dark:focus:ring-orange-600 rounded-md shadow-sm']) }}>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,15 @@
|
|||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
@php
|
||||
$current_image_uuid = auth()->user()->devices()->first()->current_screen_image;
|
||||
$current_image_path = 'images/generated/' . $current_image_uuid . '.png';
|
||||
@endphp
|
||||
@if($current_image_uuid)
|
||||
<img src="{{ asset($current_image_path) }}" alt="Current Image"/>
|
||||
@else
|
||||
{{ __("You're logged in!") }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
9
resources/views/devices.blade.php
Normal file
9
resources/views/devices.blade.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Devices') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<livewire:device-manager />
|
||||
</x-app-layout>
|
||||
30
resources/views/devices/configure.blade.php
Normal file
30
resources/views/devices/configure.blade.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Device Details: ') }} {{ $device->name }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<div class="mb-4">
|
||||
<p class="dark:text-gray-100"><strong>Name</strong> {{ $device->name }}</p>
|
||||
<p class="dark:text-gray-100"><strong>Friendly ID</strong> {{ $device->friendly_id }}</p>
|
||||
<p class="dark:text-gray-100"><strong>Mac Address</strong> {{ $device->mac_address }}</p>
|
||||
{{-- <p><strong>API Key</strong> {{ $device->api_key }}</p>--}}
|
||||
<p class="dark:text-gray-100"><strong>Refresh
|
||||
Interval</strong> {{ $device->default_refresh_interval }}</p>
|
||||
<p class="dark:text-gray-100"><strong>Battery Voltage</strong> {{ $device->last_battery_voltage }}
|
||||
</p>
|
||||
<p class="dark:text-gray-100"><strong>Wifi RSSI Level</strong> {{ $device->last_rssi_level }}</p>
|
||||
<p class="dark:text-gray-100"><strong>Firmware Version</strong> {{ $device->last_firmware_version }}
|
||||
</p>
|
||||
</div>
|
||||
@if($image)
|
||||
<img src="{{$image}}"/>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
94
resources/views/livewire/device-manager.blade.php
Normal file
94
resources/views/livewire/device-manager.blade.php
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-2xl font-semibold dark:text-gray-100">Devices</h2>
|
||||
<x-primary-button
|
||||
wire:click="toggleDeviceForm">{{ $showDeviceForm ? 'Cancel' : 'New Device' }}</x-primary-button>
|
||||
</div>
|
||||
|
||||
@if (session()->has('message'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4">
|
||||
{{ session('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($showDeviceForm)
|
||||
<div class="bg-gray-50 dark:bg-gray-800 p-6 mb-6 rounded-lg">
|
||||
<form wire:submit="createDevice">
|
||||
<div class="mb-4">
|
||||
<x-input-label for="name" :value="__('Name')"/>
|
||||
<x-text-input wire:model="name" id="name" class="block mt-1 w-full" type="text" name="name"
|
||||
autofocus/>
|
||||
@error('name') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<x-input-label for="mac_address" :value="__('Mac Adress')"/>
|
||||
<x-text-input wire:model="mac_address" id="mac_address" class="block mt-1 w-full"
|
||||
type="text" name="mac_address" autofocus/>
|
||||
@error('mac_address') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<x-input-label for="api_key" :value="__('API Key')"/>
|
||||
<x-text-input wire:model="api_key" id="api_key" class="block mt-1 w-full" type="text"
|
||||
name="api_key" autofocus/>
|
||||
@error('api_key') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<x-input-label for="friendly_id" :value="__('Friendly Id')"/>
|
||||
<x-text-input wire:model="friendly_id" id="friendly_id" class="block mt-1 w-full"
|
||||
type="text" name="friendly_id" autofocus/>
|
||||
@error('friendly_id') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<x-input-label for="default_refresh_interval" :value="__('Refresh Rate (seconds)')"/>
|
||||
<x-text-input wire:model="default_refresh_interval" id="default_refresh_interval"
|
||||
class="block mt-1 w-full" type="text" name="default_refresh_interval"
|
||||
autofocus/>
|
||||
@error('default_refresh_interval') <span
|
||||
class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<x-primary-button type="submit">Create Device</x-primary-button>
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full table-auto">
|
||||
<thead>
|
||||
<tr class="bg-gray-100 dark:bg-gray-800 ">
|
||||
<th class="px-6 py-3 text-left dark:text-gray-100">Name</th>
|
||||
<th class="px-6 py-3 text-left dark:text-gray-100">Friendly ID</th>
|
||||
<th class="px-6 py-3 text-left dark:text-gray-100">Mac Address</th>
|
||||
<th class="px-6 py-3 text-left dark:text-gray-100">Refresh</th>
|
||||
<th class="px-6 py-3 text-left dark:text-gray-100">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($devices as $device)
|
||||
<tr class="border-b">
|
||||
<td class="px-6 py-4 dark:text-gray-100">{{ $device->name }}</td>
|
||||
<td class="px-6 py-4 dark:text-gray-100">{{ $device->friendly_id }}</td>
|
||||
<td class="px-6 py-4 dark:text-gray-100">{{ $device->mac_address }}</td>
|
||||
<td class="px-6 py-4 dark:text-gray-100">{{ $device->default_refresh_interval }}</td>
|
||||
<td class="px-6 py-4 dark:text-gray-100">
|
||||
<x-secondary-button href="{{ route('devices.configure', $device) }}" wire:navigate>
|
||||
View
|
||||
</x-secondary-button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="mt-4">
|
||||
{{ $devices->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -30,8 +30,11 @@ new class extends Component
|
|||
|
||||
<!-- Navigation Links -->
|
||||
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
|
||||
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Dashboard') }}
|
||||
{{-- <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')" wire:navigate>--}}
|
||||
{{-- {{ __('Dashboard') }}--}}
|
||||
{{-- </x-nav-link>--}}
|
||||
<x-nav-link :href="route('devices')" :active="request()->routeIs('devices')" wire:navigate>
|
||||
{{ __('Devices') }}
|
||||
</x-nav-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -81,8 +84,11 @@ new class extends Component
|
|||
<!-- Responsive Navigation Menu -->
|
||||
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
|
||||
<div class="pt-2 pb-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Dashboard') }}
|
||||
{{-- <x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')" wire:navigate>--}}
|
||||
{{-- {{ __('Dashboard') }}--}}
|
||||
{{-- </x-responsive-nav-link>--}}
|
||||
<x-responsive-nav-link :href="route('devices')" :active="request()->routeIs('devices')" wire:navigate>
|
||||
{{ __('Devices') }}
|
||||
</x-responsive-nav-link>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ new #[Layout('layouts.guest')] class extends Component
|
|||
<!-- Remember Me -->
|
||||
<div class="block mt-4">
|
||||
<label for="remember" class="inline-flex items-center">
|
||||
<input wire:model="form.remember" id="remember" type="checkbox" class="rounded dark:bg-gray-900 border-gray-300 dark:border-gray-700 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800" name="remember">
|
||||
<input wire:model="form.remember" id="remember" type="checkbox" class="rounded dark:bg-gray-900 border-gray-300 dark:border-gray-700 text-orange-600 shadow-sm focus:ring-orange-500 dark:focus:ring-orange-600 dark:focus:ring-offset-gray-800" name="remember">
|
||||
<span class="ms-2 text-sm text-gray-600 dark:text-gray-400">{{ __('Remember me') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
@if (Route::has('password.request'))
|
||||
<a class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800" href="{{ route('password.request') }}" wire:navigate>
|
||||
<a class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 dark:focus:ring-offset-gray-800" href="{{ route('password.request') }}" wire:navigate>
|
||||
{{ __('Forgot your password?') }}
|
||||
</a>
|
||||
@endif
|
||||
|
|
|
|||
10
resources/views/trmnl.blade.php
Normal file
10
resources/views/trmnl.blade.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<x-trmnl::view>
|
||||
<x-trmnl::layout>
|
||||
<x-trmnl::markdown gapSize="large">
|
||||
<x-trmnl::title>Motivational Quote</x-trmnl::title>
|
||||
<x-trmnl::content>“I love inside jokes. I hope to be a part of one someday.”</x-trmnl::content>
|
||||
<x-trmnl::label variant="underline">Michael Scott</x-trmnl::label>
|
||||
</x-trmnl::markdown>
|
||||
</x-trmnl::layout>
|
||||
<x-trmnl::title-bar/>
|
||||
</x-trmnl::view>
|
||||
|
|
@ -15,128 +15,52 @@
|
|||
</head>
|
||||
<body class="antialiased font-sans">
|
||||
<div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
|
||||
<img id="background" class="absolute -left-20 top-0 max-w-[877px]" src="https://laravel.com/assets/img/welcome/background.svg" />
|
||||
<div class="relative min-h-screen flex flex-col items-center justify-center selection:bg-[#FF2D20] selection:text-white">
|
||||
<div
|
||||
class="relative min-h-screen flex flex-col items-center justify-center selection:bg-[#FF2D20] selection:text-white">
|
||||
<div class="relative w-full max-w-2xl px-6 lg:max-w-7xl">
|
||||
<header class="grid grid-cols-2 items-center gap-2 py-10 lg:grid-cols-3">
|
||||
<div class="flex lg:justify-center lg:col-start-2">
|
||||
<svg class="h-12 w-auto text-white lg:h-16 lg:text-[#FF2D20]" viewBox="0 0 62 65" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M61.8548 14.6253C61.8778 14.7102 61.8895 14.7978 61.8897 14.8858V28.5615C61.8898 28.737 61.8434 28.9095 61.7554 29.0614C61.6675 29.2132 61.5409 29.3392 61.3887 29.4265L49.9104 36.0351V49.1337C49.9104 49.4902 49.7209 49.8192 49.4118 49.9987L25.4519 63.7916C25.3971 63.8227 25.3372 63.8427 25.2774 63.8639C25.255 63.8714 25.2338 63.8851 25.2101 63.8913C25.0426 63.9354 24.8666 63.9354 24.6991 63.8913C24.6716 63.8838 24.6467 63.8689 24.6205 63.8589C24.5657 63.8389 24.5084 63.8215 24.456 63.7916L0.501061 49.9987C0.348882 49.9113 0.222437 49.7853 0.134469 49.6334C0.0465019 49.4816 0.000120578 49.3092 0 49.1337L0 8.10652C0 8.01678 0.0124642 7.92953 0.0348998 7.84477C0.0423783 7.8161 0.0598282 7.78993 0.0697995 7.76126C0.0884958 7.70891 0.105946 7.65531 0.133367 7.6067C0.152063 7.5743 0.179485 7.54812 0.20192 7.51821C0.230588 7.47832 0.256763 7.43719 0.290416 7.40229C0.319084 7.37362 0.356476 7.35243 0.388883 7.32751C0.425029 7.29759 0.457436 7.26518 0.498568 7.2415L12.4779 0.345059C12.6296 0.257786 12.8015 0.211853 12.9765 0.211853C13.1515 0.211853 13.3234 0.257786 13.475 0.345059L25.4531 7.2415H25.4556C25.4955 7.26643 25.5292 7.29759 25.5653 7.32626C25.5977 7.35119 25.6339 7.37362 25.6625 7.40104C25.6974 7.43719 25.7224 7.47832 25.7523 7.51821C25.7735 7.54812 25.8021 7.5743 25.8196 7.6067C25.8483 7.65656 25.8645 7.70891 25.8844 7.76126C25.8944 7.78993 25.9118 7.8161 25.9193 7.84602C25.9423 7.93096 25.954 8.01853 25.9542 8.10652V33.7317L35.9355 27.9844V14.8846C35.9355 14.7973 35.948 14.7088 35.9704 14.6253C35.9792 14.5954 35.9954 14.5692 36.0053 14.5405C36.0253 14.4882 36.0427 14.4346 36.0702 14.386C36.0888 14.3536 36.1163 14.3274 36.1375 14.2975C36.1674 14.2576 36.1923 14.2165 36.2272 14.1816C36.2559 14.1529 36.292 14.1317 36.3244 14.1068C36.3618 14.0769 36.3942 14.0445 36.4341 14.0208L48.4147 7.12434C48.5663 7.03694 48.7383 6.99094 48.9133 6.99094C49.0883 6.99094 49.2602 7.03694 49.4118 7.12434L61.3899 14.0208C61.4323 14.0457 61.4647 14.0769 61.5021 14.1055C61.5333 14.1305 61.5694 14.1529 61.5981 14.1803C61.633 14.2165 61.6579 14.2576 61.6878 14.2975C61.7103 14.3274 61.7377 14.3536 61.7551 14.386C61.7838 14.4346 61.8 14.4882 61.8199 14.5405C61.8312 14.5692 61.8474 14.5954 61.8548 14.6253ZM59.893 27.9844V16.6121L55.7013 19.0252L49.9104 22.3593V33.7317L59.8942 27.9844H59.893ZM47.9149 48.5566V37.1768L42.2187 40.4299L25.953 49.7133V61.2003L47.9149 48.5566ZM1.99677 9.83281V48.5566L23.9562 61.199V49.7145L12.4841 43.2219L12.4804 43.2194L12.4754 43.2169C12.4368 43.1945 12.4044 43.1621 12.3682 43.1347C12.3371 43.1097 12.3009 43.0898 12.2735 43.0624L12.271 43.0586C12.2386 43.0275 12.2162 42.9888 12.1887 42.9539C12.1638 42.9203 12.1339 42.8916 12.114 42.8567L12.1127 42.853C12.0903 42.8156 12.0766 42.7707 12.0604 42.7283C12.0442 42.6909 12.023 42.656 12.013 42.6161C12.0005 42.5688 11.998 42.5177 11.9931 42.4691C11.9881 42.4317 11.9781 42.3943 11.9781 42.3569V15.5801L6.18848 12.2446L1.99677 9.83281ZM12.9777 2.36177L2.99764 8.10652L12.9752 13.8513L22.9541 8.10527L12.9752 2.36177H12.9777ZM18.1678 38.2138L23.9574 34.8809V9.83281L19.7657 12.2459L13.9749 15.5801V40.6281L18.1678 38.2138ZM48.9133 9.14105L38.9344 14.8858L48.9133 20.6305L58.8909 14.8846L48.9133 9.14105ZM47.9149 22.3593L42.124 19.0252L37.9323 16.6121V27.9844L43.7219 31.3174L47.9149 33.7317V22.3593ZM24.9533 47.987L39.59 39.631L46.9065 35.4555L36.9352 29.7145L25.4544 36.3242L14.9907 42.3482L24.9533 47.987Z" fill="currentColor"/></svg>
|
||||
<svg class="h-12 w-auto" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_870_1047)">
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M27.3739 23.0034L19.0088 23.7616L18.6349 19.6023L27 18.8441L27.3739 23.0034Z"
|
||||
fill="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
<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="#F8654B"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_870_1047">
|
||||
<rect width="30" height="30" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
@if (Route::has('login'))
|
||||
<livewire:welcome.navigation/>
|
||||
@endif
|
||||
</header>
|
||||
|
||||
<main class="mt-6">
|
||||
<div class="grid gap-6 lg:grid-cols-2 lg:gap-8">
|
||||
<a
|
||||
href="https://laravel.com/docs"
|
||||
id="docs-card"
|
||||
class="flex flex-col items-start gap-6 overflow-hidden rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] transition duration-300 hover:text-black/70 hover:ring-black/20 focus:outline-none focus-visible:ring-[#FF2D20] md:row-span-3 lg:p-10 lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800 dark:hover:text-white/70 dark:hover:ring-zinc-700 dark:focus-visible:ring-[#FF2D20]"
|
||||
>
|
||||
<div id="screenshot-container" class="relative flex w-full flex-1 items-stretch">
|
||||
<img
|
||||
src="https://laravel.com/assets/img/welcome/docs-light.svg"
|
||||
alt="Laravel documentation screenshot"
|
||||
class="aspect-video h-full w-full flex-1 rounded-[10px] object-top object-cover drop-shadow-[0px_4px_34px_rgba(0,0,0,0.06)] dark:hidden"
|
||||
onerror="
|
||||
document.getElementById('screenshot-container').classList.add('!hidden');
|
||||
document.getElementById('docs-card').classList.add('!row-span-1');
|
||||
document.getElementById('docs-card-content').classList.add('!flex-row');
|
||||
document.getElementById('background').classList.add('!hidden');
|
||||
"
|
||||
/>
|
||||
<img
|
||||
src="https://laravel.com/assets/img/welcome/docs-dark.svg"
|
||||
alt="Laravel documentation screenshot"
|
||||
class="hidden aspect-video h-full w-full flex-1 rounded-[10px] object-top object-cover drop-shadow-[0px_4px_34px_rgba(0,0,0,0.25)] dark:block"
|
||||
/>
|
||||
<div
|
||||
class="absolute -bottom-16 -left-16 h-40 w-[calc(100%+8rem)] bg-gradient-to-b from-transparent via-white to-white dark:via-zinc-900 dark:to-zinc-900"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="relative flex items-center gap-6 lg:items-end">
|
||||
<div id="docs-card-content" class="flex items-start gap-6 lg:flex-col">
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#FF2D20" d="M23 4a1 1 0 0 0-1.447-.894L12.224 7.77a.5.5 0 0 1-.448 0L2.447 3.106A1 1 0 0 0 1 4v13.382a1.99 1.99 0 0 0 1.105 1.79l9.448 4.728c.14.065.293.1.447.1.154-.005.306-.04.447-.105l9.453-4.724a1.99 1.99 0 0 0 1.1-1.789V4ZM3 6.023a.25.25 0 0 1 .362-.223l7.5 3.75a.251.251 0 0 1 .138.223v11.2a.25.25 0 0 1-.362.224l-7.5-3.75a.25.25 0 0 1-.138-.22V6.023Zm18 11.2a.25.25 0 0 1-.138.224l-7.5 3.75a.249.249 0 0 1-.329-.099.249.249 0 0 1-.033-.12V9.772a.251.251 0 0 1 .138-.224l7.5-3.75a.25.25 0 0 1 .362.224v11.2Z"/><path fill="#FF2D20" d="m3.55 1.893 8 4.048a1.008 1.008 0 0 0 .9 0l8-4.048a1 1 0 0 0-.9-1.785l-7.322 3.706a.506.506 0 0 1-.452 0L4.454.108a1 1 0 0 0-.9 1.785H3.55Z"/></svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5 lg:pt-0">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Documentation</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laravel has wonderful documentation covering every aspect of the framework. Whether you are a newcomer or have prior experience with Laravel, we recommend reading our documentation from beginning to end.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg class="size-6 shrink-0 stroke-[#FF2D20]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75"/></svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://laracasts.com"
|
||||
class="flex items-start gap-4 rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] transition duration-300 hover:text-black/70 hover:ring-black/20 focus:outline-none focus-visible:ring-[#FF2D20] lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800 dark:hover:text-white/70 dark:hover:ring-zinc-700 dark:focus-visible:ring-[#FF2D20]"
|
||||
>
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><g fill="#FF2D20"><path d="M24 8.25a.5.5 0 0 0-.5-.5H.5a.5.5 0 0 0-.5.5v12a2.5 2.5 0 0 0 2.5 2.5h19a2.5 2.5 0 0 0 2.5-2.5v-12Zm-7.765 5.868a1.221 1.221 0 0 1 0 2.264l-6.626 2.776A1.153 1.153 0 0 1 8 18.123v-5.746a1.151 1.151 0 0 1 1.609-1.035l6.626 2.776ZM19.564 1.677a.25.25 0 0 0-.177-.427H15.6a.106.106 0 0 0-.072.03l-4.54 4.543a.25.25 0 0 0 .177.427h3.783c.027 0 .054-.01.073-.03l4.543-4.543ZM22.071 1.318a.047.047 0 0 0-.045.013l-4.492 4.492a.249.249 0 0 0 .038.385.25.25 0 0 0 .14.042h5.784a.5.5 0 0 0 .5-.5v-2a2.5 2.5 0 0 0-1.925-2.432ZM13.014 1.677a.25.25 0 0 0-.178-.427H9.101a.106.106 0 0 0-.073.03l-4.54 4.543a.25.25 0 0 0 .177.427H8.4a.106.106 0 0 0 .073-.03l4.54-4.543ZM6.513 1.677a.25.25 0 0 0-.177-.427H2.5A2.5 2.5 0 0 0 0 3.75v2a.5.5 0 0 0 .5.5h1.4a.106.106 0 0 0 .073-.03l4.54-4.543Z"/></g></svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Laracasts</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<svg class="size-6 shrink-0 self-center stroke-[#FF2D20]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75"/></svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://laravel-news.com"
|
||||
class="flex items-start gap-4 rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] transition duration-300 hover:text-black/70 hover:ring-black/20 focus:outline-none focus-visible:ring-[#FF2D20] lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800 dark:hover:text-white/70 dark:hover:ring-zinc-700 dark:focus-visible:ring-[#FF2D20]"
|
||||
>
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><g fill="#FF2D20"><path d="M8.75 4.5H5.5c-.69 0-1.25.56-1.25 1.25v4.75c0 .69.56 1.25 1.25 1.25h3.25c.69 0 1.25-.56 1.25-1.25V5.75c0-.69-.56-1.25-1.25-1.25Z"/><path d="M24 10a3 3 0 0 0-3-3h-2V2.5a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2V20a3.5 3.5 0 0 0 3.5 3.5h17A3.5 3.5 0 0 0 24 20V10ZM3.5 21.5A1.5 1.5 0 0 1 2 20V3a.5.5 0 0 1 .5-.5h14a.5.5 0 0 1 .5.5v17c0 .295.037.588.11.874a.5.5 0 0 1-.484.625L3.5 21.5ZM22 20a1.5 1.5 0 1 1-3 0V9.5a.5.5 0 0 1 .5-.5H21a1 1 0 0 1 1 1v10Z"/><path d="M12.751 6.047h2a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-2A.75.75 0 0 1 12 7.3v-.5a.75.75 0 0 1 .751-.753ZM12.751 10.047h2a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-2A.75.75 0 0 1 12 11.3v-.5a.75.75 0 0 1 .751-.753ZM4.751 14.047h10a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-10A.75.75 0 0 1 4 15.3v-.5a.75.75 0 0 1 .751-.753ZM4.75 18.047h7.5a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-7.5A.75.75 0 0 1 4 19.3v-.5a.75.75 0 0 1 .75-.753Z"/></g></svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Laravel News</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<svg class="size-6 shrink-0 self-center stroke-[#FF2D20]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75"/></svg>
|
||||
</a>
|
||||
|
||||
<div class="flex items-start gap-4 rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800">
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<g fill="#FF2D20">
|
||||
<path
|
||||
d="M16.597 12.635a.247.247 0 0 0-.08-.237 2.234 2.234 0 0 1-.769-1.68c.001-.195.03-.39.084-.578a.25.25 0 0 0-.09-.267 8.8 8.8 0 0 0-4.826-1.66.25.25 0 0 0-.268.181 2.5 2.5 0 0 1-2.4 1.824.045.045 0 0 0-.045.037 12.255 12.255 0 0 0-.093 3.86.251.251 0 0 0 .208.214c2.22.366 4.367 1.08 6.362 2.118a.252.252 0 0 0 .32-.079 10.09 10.09 0 0 0 1.597-3.733ZM13.616 17.968a.25.25 0 0 0-.063-.407A19.697 19.697 0 0 0 8.91 15.98a.25.25 0 0 0-.287.325c.151.455.334.898.548 1.328.437.827.981 1.594 1.619 2.28a.249.249 0 0 0 .32.044 29.13 29.13 0 0 0 2.506-1.99ZM6.303 14.105a.25.25 0 0 0 .265-.274 13.048 13.048 0 0 1 .205-4.045.062.062 0 0 0-.022-.07 2.5 2.5 0 0 1-.777-.982.25.25 0 0 0-.271-.149 11 11 0 0 0-5.6 2.815.255.255 0 0 0-.075.163c-.008.135-.02.27-.02.406.002.8.084 1.598.246 2.381a.25.25 0 0 0 .303.193 19.924 19.924 0 0 1 5.746-.438ZM9.228 20.914a.25.25 0 0 0 .1-.393 11.53 11.53 0 0 1-1.5-2.22 12.238 12.238 0 0 1-.91-2.465.248.248 0 0 0-.22-.187 18.876 18.876 0 0 0-5.69.33.249.249 0 0 0-.179.336c.838 2.142 2.272 4 4.132 5.353a.254.254 0 0 0 .15.048c1.41-.01 2.807-.282 4.117-.802ZM18.93 12.957l-.005-.008a.25.25 0 0 0-.268-.082 2.21 2.21 0 0 1-.41.081.25.25 0 0 0-.217.2c-.582 2.66-2.127 5.35-5.75 7.843a.248.248 0 0 0-.09.299.25.25 0 0 0 .065.091 28.703 28.703 0 0 0 2.662 2.12.246.246 0 0 0 .209.037c2.579-.701 4.85-2.242 6.456-4.378a.25.25 0 0 0 .048-.189 13.51 13.51 0 0 0-2.7-6.014ZM5.702 7.058a.254.254 0 0 0 .2-.165A2.488 2.488 0 0 1 7.98 5.245a.093.093 0 0 0 .078-.062 19.734 19.734 0 0 1 3.055-4.74.25.25 0 0 0-.21-.41 12.009 12.009 0 0 0-10.4 8.558.25.25 0 0 0 .373.281 12.912 12.912 0 0 1 4.826-1.814ZM10.773 22.052a.25.25 0 0 0-.28-.046c-.758.356-1.55.635-2.365.833a.25.25 0 0 0-.022.48c1.252.43 2.568.65 3.893.65.1 0 .2 0 .3-.008a.25.25 0 0 0 .147-.444c-.526-.424-1.1-.917-1.673-1.465ZM18.744 8.436a.249.249 0 0 0 .15.228 2.246 2.246 0 0 1 1.352 2.054c0 .337-.08.67-.23.972a.25.25 0 0 0 .042.28l.007.009a15.016 15.016 0 0 1 2.52 4.6.25.25 0 0 0 .37.132.25.25 0 0 0 .096-.114c.623-1.464.944-3.039.945-4.63a12.005 12.005 0 0 0-5.78-10.258.25.25 0 0 0-.373.274c.547 2.109.85 4.274.901 6.453ZM9.61 5.38a.25.25 0 0 0 .08.31c.34.24.616.561.8.935a.25.25 0 0 0 .3.127.631.631 0 0 1 .206-.034c2.054.078 4.036.772 5.69 1.991a.251.251 0 0 0 .267.024c.046-.024.093-.047.141-.067a.25.25 0 0 0 .151-.23A29.98 29.98 0 0 0 15.957.764a.25.25 0 0 0-.16-.164 11.924 11.924 0 0 0-2.21-.518.252.252 0 0 0-.215.076A22.456 22.456 0 0 0 9.61 5.38Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Vibrant Ecosystem</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laravel's robust library of first-party tools and libraries, such as <a href="https://forge.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white dark:focus-visible:ring-[#FF2D20]">Forge</a>, <a href="https://vapor.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Vapor</a>, <a href="https://nova.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Nova</a>, <a href="https://envoyer.io" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Envoyer</a>, and <a href="https://herd.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Herd</a> help you take your projects to the next level. Pair them with powerful open source libraries like <a href="https://laravel.com/docs/billing" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Cashier</a>, <a href="https://laravel.com/docs/dusk" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Dusk</a>, <a href="https://laravel.com/docs/broadcasting" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Echo</a>, <a href="https://laravel.com/docs/horizon" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Horizon</a>, <a href="https://laravel.com/docs/sanctum" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Sanctum</a>, <a href="https://laravel.com/docs/telescope" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Telescope</a>, and more.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="py-16 text-center text-sm text-black dark:text-white/70">
|
||||
@if(app()->environment('local'))
|
||||
Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
|
||||
@endif
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
81
routes/api.php
Normal file
81
routes/api.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Device;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/display', function (Request $request) {
|
||||
|
||||
$mac_address = $request->header('id');
|
||||
$access_token = $request->header('access-token');
|
||||
|
||||
$device = Device::where('mac_address', $mac_address)
|
||||
->where('api_key', $access_token)
|
||||
->first();
|
||||
if (! $device) {
|
||||
return response()->json([
|
||||
'message' => 'MAC Address not registered or invalid access token',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$device->update([
|
||||
'last_rssi_level' => $request->header('rssi'),
|
||||
'last_battery_voltage' => $request->header('battery_voltage'),
|
||||
'last_firmware_version' => $request->header('fw-version'),
|
||||
]);
|
||||
|
||||
$image_path = collect(\Storage::disk('public')->files('/images/generated'))->first(fn ($file) => \Str::endsWith($file, '.bmp'));
|
||||
$filename = basename($image_path);
|
||||
|
||||
return response()->json([
|
||||
'status' => '0',
|
||||
'image_url' => url('storage/'.$image_path),
|
||||
'filename' => $filename,
|
||||
'refresh_rate' => 900,
|
||||
'reset_firmware' => false,
|
||||
'update_firmware' => false,
|
||||
'firmware_url' => null,
|
||||
'special_function' => 'sleep',
|
||||
]);
|
||||
});
|
||||
|
||||
Route::get('/setup', function (Request $request) {
|
||||
$mac_address = $request->header('id');
|
||||
|
||||
if (! $mac_address) {
|
||||
return response()->json([
|
||||
'message' => 'MAC Address not registered',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$device = Device::where('mac_address', $mac_address)->first();
|
||||
|
||||
if (! $device) {
|
||||
return response()->json([
|
||||
'message' => 'MAC Address not registered',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'api_key' => $device->api_key,
|
||||
'friendly_id' => $device->friendly_id,
|
||||
'image_url' => url('storage/images/setup-logo.png'),
|
||||
'message' => 'Welcome to TRMNL BYOS',
|
||||
]);
|
||||
});
|
||||
|
||||
Route::post('/log', function (Request $request) {
|
||||
$logs = $request->json('log.logs_array', []);
|
||||
|
||||
foreach ($logs as $log) {
|
||||
\Log::info('Device Log', $log);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => '0',
|
||||
]);
|
||||
});
|
||||
|
||||
Route::get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
})->middleware('auth:sanctum');
|
||||
|
|
@ -5,8 +5,8 @@ use Illuminate\Support\Facades\Route;
|
|||
use Livewire\Volt\Volt;
|
||||
|
||||
Route::middleware('guest')->group(function () {
|
||||
Volt::route('register', 'pages.auth.register')
|
||||
->name('register');
|
||||
// Volt::route('register', 'pages.auth.register')
|
||||
// ->name('register');
|
||||
|
||||
Volt::route('login', 'pages.auth.login')
|
||||
->name('login');
|
||||
|
|
|
|||
|
|
@ -12,4 +12,19 @@ Route::view('profile', 'profile')
|
|||
->middleware(['auth'])
|
||||
->name('profile');
|
||||
|
||||
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
|
||||
Route::get('/devices', function () {
|
||||
return view('devices');
|
||||
})->name('devices');
|
||||
|
||||
Route::get('/devices/{device}/configure', function (App\Models\Device $device) {
|
||||
$current_image_uuid = auth()->user()->devices()->find($device->id)->current_screen_image;
|
||||
$current_image_path = 'images/generated/'.$current_image_uuid.'.png';
|
||||
|
||||
return view('devices.configure', compact('device'), [
|
||||
'image' => ($current_image_uuid) ? url($current_image_path) : null,
|
||||
]);
|
||||
})->name('devices.configure');
|
||||
});
|
||||
|
||||
require __DIR__.'/auth.php';
|
||||
|
|
|
|||
1
storage/app/public/.gitignore
vendored
1
storage/app/public/.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
*
|
||||
!images/
|
||||
!.gitignore
|
||||
|
|
|
|||
2
storage/app/public/images/generated/.gitignore
vendored
Normal file
2
storage/app/public/images/generated/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
BIN
storage/app/public/images/setup-logo.bmp
Normal file
BIN
storage/app/public/images/setup-logo.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
|
|
@ -4,13 +4,13 @@ namespace Tests\Feature\Auth;
|
|||
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
test('registration screen can be rendered', function () {
|
||||
$response = $this->get('/register');
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertSeeVolt('pages.auth.register');
|
||||
});
|
||||
// test('registration screen can be rendered', function () {
|
||||
// $response = $this->get('/register');
|
||||
//
|
||||
// $response
|
||||
// ->assertOk()
|
||||
// ->assertSeeVolt('pages.auth.register');
|
||||
// });
|
||||
|
||||
test('new users can register', function () {
|
||||
$component = Volt::test('pages.auth.register')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue