mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
add features
* feat: autojoin toggle * feat: auto add devices * feat: proxy feature * feat: support puppeteer in docker * feat: toggle to activate cloud proxy * feat: relay device information * feat: relay logs to cloud * feat: migrate on start * feat: calculate battery state, wifi signal * feat: eye candy for configure view * feat: update via api
This commit is contained in:
parent
d4eb832186
commit
715e6a2562
53 changed files with 1459 additions and 460 deletions
24
.dockerignore
Normal file
24
.dockerignore
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/.phpunit.cache
|
||||||
|
/node_modules
|
||||||
|
/public/build
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/storage/*.key
|
||||||
|
/storage/pail
|
||||||
|
/vendor
|
||||||
|
.env
|
||||||
|
.env.backup
|
||||||
|
.env.production
|
||||||
|
.phpactor.json
|
||||||
|
.phpunit.result.cache
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
auth.json
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
/.fleet
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
/.zed
|
||||||
|
/bootstrap/cache/*
|
||||||
|
/database/database.sqlite
|
||||||
14
.env.example
14
.env.example
|
|
@ -1,7 +1,7 @@
|
||||||
APP_NAME=Laravel
|
APP_NAME=TrmnlServer
|
||||||
APP_ENV=local
|
APP_ENV=production
|
||||||
APP_KEY=
|
APP_KEY=base64:zzPXBQPlgn0NHwVBTVG0B//8P/PVwVnBp2gk0ZWR0+k=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=false
|
||||||
APP_TIMEZONE=UTC
|
APP_TIMEZONE=UTC
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ PHP_CLI_SERVER_WORKERS=4
|
||||||
BCRYPT_ROUNDS=12
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
LOG_STACK=single
|
LOG_STACK=single,stderr
|
||||||
LOG_DEPRECATIONS_CHANNEL=null
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
|
@ -64,3 +64,7 @@ AWS_BUCKET=
|
||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
TRMNL_PROXY_BASE_URL=https://trmnl.app
|
||||||
|
TRMNL_PROXY_REFRESH_MINUTES=15
|
||||||
|
REGISTRATION_ENABLED=1
|
||||||
|
|
|
||||||
68
Dockerfile
Normal file
68
Dockerfile
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
FROM php:8.3-fpm-alpine3.20
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
nginx \
|
||||||
|
supervisor \
|
||||||
|
libpq \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
zip \
|
||||||
|
unzip \
|
||||||
|
imagemagick-dev \
|
||||||
|
chromium
|
||||||
|
|
||||||
|
# Configure Chromium Path
|
||||||
|
ENV PUPPETEER_EXECUTABLE_PATH /usr/bin/chromium
|
||||||
|
ENV PUPPETEER_DOCKER 1
|
||||||
|
|
||||||
|
#RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS imagemagick-dev \
|
||||||
|
#&& pecl install imagick \
|
||||||
|
#&& docker-php-ext-enable imagick \
|
||||||
|
#&& apk del .build-deps \
|
||||||
|
|
||||||
|
#RUN docker-php-ext-install imagick \
|
||||||
|
# && docker-php-ext-enable imagick
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/src/php/ext/imagick
|
||||||
|
RUN chmod 777 /usr/src/php/ext/imagick
|
||||||
|
RUN curl -fsSL https://github.com/Imagick/imagick/archive/refs/tags/3.7.0.tar.gz | tar xvz -C "/usr/src/php/ext/imagick" --strip 1
|
||||||
|
|
||||||
|
# Install PHP extensions
|
||||||
|
RUN docker-php-ext-install opcache imagick
|
||||||
|
|
||||||
|
# Install composer
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
# Copy application files
|
||||||
|
COPY --chown=www-data:www-data . .
|
||||||
|
COPY --chown=www-data:www-data ./.env.example ./.env
|
||||||
|
|
||||||
|
# Install application dependencies
|
||||||
|
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
# Copy configuration files
|
||||||
|
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
|
||||||
|
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
COPY docker/php.ini /usr/local/etc/php/conf.d/custom.ini
|
||||||
|
|
||||||
|
# Create required directories
|
||||||
|
RUN mkdir -p /var/log/supervisor \
|
||||||
|
&& mkdir -p storage/logs \
|
||||||
|
&& mkdir -p storage/framework/{cache,sessions,views} \
|
||||||
|
&& chmod -R 775 storage \
|
||||||
|
&& chmod -R 775 bootstrap/cache \
|
||||||
|
&& touch database/database.sqlite \
|
||||||
|
&& chmod -R 777 database
|
||||||
|
|
||||||
|
# Expose port 80
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Start supervisor
|
||||||
|
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
18
app/Console/Commands/FetchProxyCloudResponsesCommand.php
Normal file
18
app/Console/Commands/FetchProxyCloudResponsesCommand.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Jobs\FetchProxyCloudResponses;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class FetchProxyCloudResponsesCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'trmnl:cloud:proxy';
|
||||||
|
|
||||||
|
protected $description = 'Fetch Cloud Screens';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
FetchProxyCloudResponses::dispatchSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Models\Device;
|
use App\Jobs\GenerateScreenJob;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Ramsey\Uuid\Uuid;
|
|
||||||
use Spatie\Browsershot\Browsershot;
|
|
||||||
|
|
||||||
class ScreenGeneratorCommand extends Command
|
class ScreenGeneratorCommand extends Command
|
||||||
{
|
{
|
||||||
|
|
@ -22,101 +19,28 @@ class ScreenGeneratorCommand extends Command
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = '';
|
protected $description = 'Generate a screen for a terminal device';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
|
||||||
$deviceId = $this->argument('deviceId');
|
$deviceId = $this->argument('deviceId');
|
||||||
$view = $this->argument('view');
|
$view = $this->argument('view');
|
||||||
|
|
||||||
$uuid = Uuid::uuid4()->toString();
|
|
||||||
$pngPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.png');
|
|
||||||
$bmpPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.bmp');
|
|
||||||
|
|
||||||
// Generate PNG
|
|
||||||
try {
|
try {
|
||||||
Browsershot::html(view($view)->render())
|
$markup = view($view)->render();
|
||||||
->windowSize(800, 480)
|
} catch (\Throwable $e) {
|
||||||
->save($pngPath);
|
$this->error('Failed to render view: '.$e->getMessage());
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('Failed to generate PNG: '.$e->getMessage());
|
|
||||||
|
|
||||||
return;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
GenerateScreenJob::dispatchSync($deviceId, $markup);
|
||||||
$this->convertToBmpImageMagick($pngPath, $bmpPath);
|
|
||||||
|
|
||||||
} catch (\ImagickException $e) {
|
$this->info('Screen generation job finished.');
|
||||||
$this->error('Failed to convert image to BMP: '.$e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
Device::find($deviceId)->update(['current_screen_image' => $uuid]);
|
return 0;
|
||||||
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
95
app/Jobs/FetchProxyCloudResponses.php
Normal file
95
app/Jobs/FetchProxyCloudResponses.php
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class FetchProxyCloudResponses implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
Device::where('proxy_cloud', true)->each(function ($device) {
|
||||||
|
try {
|
||||||
|
$response = 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');
|
||||||
|
|
||||||
|
$device->update([
|
||||||
|
'proxy_cloud_response' => $response->json(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$imageUrl = $response->json('image_url');
|
||||||
|
$filename = $response->json('filename');
|
||||||
|
|
||||||
|
\Log::info('Response data: '.$imageUrl);
|
||||||
|
if (isset($imageUrl)) {
|
||||||
|
try {
|
||||||
|
$imageContents = Http::get($imageUrl)->body();
|
||||||
|
if (! Storage::disk('public')->exists("images/generated/{$filename}.bmp")) {
|
||||||
|
Storage::disk('public')->put(
|
||||||
|
"images/generated/{$filename}.bmp",
|
||||||
|
$imageContents
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$device->update([
|
||||||
|
'current_screen_image' => $filename,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error("Failed to download and save image for device: {$device->mac_address}", [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info("Successfully updated proxy cloud response for device: {$device->mac_address}");
|
||||||
|
|
||||||
|
if ($device->last_log_request) {
|
||||||
|
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',
|
||||||
|
])->post(config('services.trmnl.proxy_base_url').'/api/log', $device->last_log_request);
|
||||||
|
|
||||||
|
$device->update([
|
||||||
|
'last_log_request' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error("Failed to fetch proxy cloud response for device: {$device->mac_address}", [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
89
app/Jobs/GenerateScreenJob.php
Normal file
89
app/Jobs/GenerateScreenJob.php
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Spatie\Browsershot\Browsershot;
|
||||||
|
|
||||||
|
class GenerateScreenJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly int $deviceId,
|
||||||
|
private readonly string $markup
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$uuid = Uuid::uuid4()->toString();
|
||||||
|
$pngPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.png');
|
||||||
|
$bmpPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.bmp');
|
||||||
|
|
||||||
|
// Generate PNG
|
||||||
|
try {
|
||||||
|
Browsershot::html($this->markup)
|
||||||
|
->setOption('args', config('app.puppeteer_docker') ? ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu'] : [])
|
||||||
|
->windowSize(800, 480)
|
||||||
|
->save($pngPath);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \RuntimeException('Failed to generate PNG: '.$e->getMessage(), 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->convertToBmpImageMagick($pngPath, $bmpPath);
|
||||||
|
} catch (\ImagickException $e) {
|
||||||
|
throw new \RuntimeException('Failed to convert image to BMP: '.$e->getMessage(), 0, $e);
|
||||||
|
}
|
||||||
|
Device::find($this->deviceId)->update(['current_screen_image' => $uuid]);
|
||||||
|
\Log::info("Device $this->deviceId: updated with new image: $uuid");
|
||||||
|
|
||||||
|
$this->cleanupFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \ImagickException
|
||||||
|
*/
|
||||||
|
private 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/Livewire/Actions/DeviceAutoJoin.php
Normal file
37
app/Livewire/Actions/DeviceAutoJoin.php
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Actions;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class DeviceAutoJoin extends Component
|
||||||
|
{
|
||||||
|
public bool $deviceAutojoin = false;
|
||||||
|
|
||||||
|
public bool $isFirstUser = false;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->deviceAutojoin = auth()->user()->assign_new_devices;
|
||||||
|
$this->isFirstUser = auth()->user()->id === 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updating($name, $value)
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'deviceAutojoin' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($name === 'deviceAutojoin') {
|
||||||
|
auth()->user()->update([
|
||||||
|
'assign_new_devices' => $value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.actions.device-auto-join');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
<?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();
|
|
||||||
\Flux::modal('create-device')->close();
|
|
||||||
session()->flash('message', 'Device created successfully.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,4 +10,44 @@ class Device extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'proxy_cloud' => 'boolean',
|
||||||
|
'last_log_request' => 'json',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getBatteryPercentAttribute()
|
||||||
|
{
|
||||||
|
$volts = $this->last_battery_voltage;
|
||||||
|
|
||||||
|
// Define min and max voltage for Li-ion battery (3.0V empty, 4.2V full)
|
||||||
|
$min_volt = 3.0;
|
||||||
|
$max_volt = 4.2;
|
||||||
|
|
||||||
|
// Ensure the voltage is within range
|
||||||
|
if ($volts <= $min_volt) {
|
||||||
|
return 0;
|
||||||
|
} elseif ($volts >= $max_volt) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate percentage
|
||||||
|
$percent = (($volts - $min_volt) / ($max_volt - $min_volt)) * 100;
|
||||||
|
|
||||||
|
return round($percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWifiStrenghAttribute()
|
||||||
|
{
|
||||||
|
$rssi = $this->last_rssi_level;
|
||||||
|
if ($rssi >= 0) {
|
||||||
|
return 0; // No signal (0 bars)
|
||||||
|
} elseif ($rssi <= -80) {
|
||||||
|
return 1; // Weak signal (1 bar)
|
||||||
|
} elseif ($rssi <= -60) {
|
||||||
|
return 2; // Moderate signal (2 bars)
|
||||||
|
} else {
|
||||||
|
return 3; // Strong signal (3 bars)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class User extends Authenticatable // implements MustVerifyEmail
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
'assign_new_devices',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,6 +47,7 @@ class User extends Authenticatable // implements MustVerifyEmail
|
||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
|
'assign_new_devices' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ class AppServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
if (app()->isProduction() && config('app.force_https')) {
|
||||||
|
\URL::forceScheme('https');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
|
|
@ -12,7 +14,10 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware) {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
//
|
$middleware->alias([
|
||||||
|
'abilities' => CheckAbilities::class,
|
||||||
|
'ability' => CheckForAnyAbility::class,
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions) {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
//
|
//
|
||||||
|
|
|
||||||
114
composer.lock
generated
114
composer.lock
generated
|
|
@ -87,16 +87,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
"version": "0.12.1",
|
"version": "0.12.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/brick/math.git",
|
"url": "https://github.com/brick/math.git",
|
||||||
"reference": "f510c0a40911935b77b86859eb5223d58d660df1"
|
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
|
"url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40",
|
||||||
"reference": "f510c0a40911935b77b86859eb5223d58d660df1",
|
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"php-coveralls/php-coveralls": "^2.2",
|
"php-coveralls/php-coveralls": "^2.2",
|
||||||
"phpunit/phpunit": "^10.1",
|
"phpunit/phpunit": "^10.1",
|
||||||
"vimeo/psalm": "5.16.0"
|
"vimeo/psalm": "6.8.8"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
|
@ -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.1"
|
"source": "https://github.com/brick/math/tree/0.12.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-11-29T23:19:16+00:00"
|
"time": "2025-02-26T10:21:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "carbonphp/carbon-doctrine-types",
|
"name": "carbonphp/carbon-doctrine-types",
|
||||||
|
|
@ -2295,16 +2295,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/flux",
|
"name": "livewire/flux",
|
||||||
"version": "v2.0.2",
|
"version": "v2.0.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/livewire/flux.git",
|
"url": "https://github.com/livewire/flux.git",
|
||||||
"reference": "424d88f7e1c68730edc56fd8041568c135c3d8ab"
|
"reference": "dec010f09419cd9d9930abc4b304802c379be57e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/livewire/flux/zipball/424d88f7e1c68730edc56fd8041568c135c3d8ab",
|
"url": "https://api.github.com/repos/livewire/flux/zipball/dec010f09419cd9d9930abc4b304802c379be57e",
|
||||||
"reference": "424d88f7e1c68730edc56fd8041568c135c3d8ab",
|
"reference": "dec010f09419cd9d9930abc4b304802c379be57e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -2352,22 +2352,22 @@
|
||||||
],
|
],
|
||||||
"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.2"
|
"source": "https://github.com/livewire/flux/tree/v2.0.3"
|
||||||
},
|
},
|
||||||
"time": "2025-02-21T13:00:37+00:00"
|
"time": "2025-02-26T00:29:58+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/livewire",
|
"name": "livewire/livewire",
|
||||||
"version": "v3.5.20",
|
"version": "v3.6.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/livewire/livewire.git",
|
"url": "https://github.com/livewire/livewire.git",
|
||||||
"reference": "509f2258c51741f6d06deb65d4437654520694e6"
|
"reference": "4c66b569cb729ba9b2f4a3c4e79f50fd8f2b71d1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/509f2258c51741f6d06deb65d4437654520694e6",
|
"url": "https://api.github.com/repos/livewire/livewire/zipball/4c66b569cb729ba9b2f4a3c4e79f50fd8f2b71d1",
|
||||||
"reference": "509f2258c51741f6d06deb65d4437654520694e6",
|
"reference": "4c66b569cb729ba9b2f4a3c4e79f50fd8f2b71d1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -2422,7 +2422,7 @@
|
||||||
"description": "A front-end framework for Laravel.",
|
"description": "A front-end framework for Laravel.",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/livewire/livewire/issues",
|
"issues": "https://github.com/livewire/livewire/issues",
|
||||||
"source": "https://github.com/livewire/livewire/tree/v3.5.20"
|
"source": "https://github.com/livewire/livewire/tree/v3.6.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -2430,7 +2430,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-02-13T21:05:24+00:00"
|
"time": "2025-02-26T00:57:32+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/volt",
|
"name": "livewire/volt",
|
||||||
|
|
@ -4287,16 +4287,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/error-handler",
|
"name": "symfony/error-handler",
|
||||||
"version": "v7.2.3",
|
"version": "v7.2.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/error-handler.git",
|
"url": "https://github.com/symfony/error-handler.git",
|
||||||
"reference": "959a74d044a6db21f4caa6d695648dcb5584cb49"
|
"reference": "aabf79938aa795350c07ce6464dd1985607d95d5"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/error-handler/zipball/959a74d044a6db21f4caa6d695648dcb5584cb49",
|
"url": "https://api.github.com/repos/symfony/error-handler/zipball/aabf79938aa795350c07ce6464dd1985607d95d5",
|
||||||
"reference": "959a74d044a6db21f4caa6d695648dcb5584cb49",
|
"reference": "aabf79938aa795350c07ce6464dd1985607d95d5",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -4342,7 +4342,7 @@
|
||||||
"description": "Provides tools to manage errors and ease debugging PHP code",
|
"description": "Provides tools to manage errors and ease debugging PHP code",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/error-handler/tree/v7.2.3"
|
"source": "https://github.com/symfony/error-handler/tree/v7.2.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -4358,7 +4358,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-01-07T09:39:55+00:00"
|
"time": "2025-02-02T20:27:07+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/event-dispatcher",
|
"name": "symfony/event-dispatcher",
|
||||||
|
|
@ -4660,16 +4660,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/http-kernel",
|
"name": "symfony/http-kernel",
|
||||||
"version": "v7.2.3",
|
"version": "v7.2.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/http-kernel.git",
|
"url": "https://github.com/symfony/http-kernel.git",
|
||||||
"reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b"
|
"reference": "9f1103734c5789798fefb90e91de4586039003ed"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b",
|
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f1103734c5789798fefb90e91de4586039003ed",
|
||||||
"reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b",
|
"reference": "9f1103734c5789798fefb90e91de4586039003ed",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -4754,7 +4754,7 @@
|
||||||
"description": "Provides a structured process for converting a Request into a Response",
|
"description": "Provides a structured process for converting a Request into a Response",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/http-kernel/tree/v7.2.3"
|
"source": "https://github.com/symfony/http-kernel/tree/v7.2.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -4770,7 +4770,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-01-29T07:40:13+00:00"
|
"time": "2025-02-26T11:01:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/mailer",
|
"name": "symfony/mailer",
|
||||||
|
|
@ -4854,16 +4854,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/mime",
|
"name": "symfony/mime",
|
||||||
"version": "v7.2.3",
|
"version": "v7.2.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/mime.git",
|
"url": "https://github.com/symfony/mime.git",
|
||||||
"reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204"
|
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/mime/zipball/2fc3b4bd67e4747e45195bc4c98bea4628476204",
|
"url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||||
"reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204",
|
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -4918,7 +4918,7 @@
|
||||||
"mime-type"
|
"mime-type"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/mime/tree/v7.2.3"
|
"source": "https://github.com/symfony/mime/tree/v7.2.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -4934,7 +4934,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-01-27T11:08:17+00:00"
|
"time": "2025-02-19T08:51:20+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
|
|
@ -5574,16 +5574,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/process",
|
"name": "symfony/process",
|
||||||
"version": "v7.2.0",
|
"version": "v7.2.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/process.git",
|
"url": "https://github.com/symfony/process.git",
|
||||||
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
|
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
|
"url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
|
||||||
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
|
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -5615,7 +5615,7 @@
|
||||||
"description": "Executes commands in sub-processes",
|
"description": "Executes commands in sub-processes",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/process/tree/v7.2.0"
|
"source": "https://github.com/symfony/process/tree/v7.2.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -5631,7 +5631,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-11-06T14:24:19+00:00"
|
"time": "2025-02-05T08:33:46+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/routing",
|
"name": "symfony/routing",
|
||||||
|
|
@ -5886,16 +5886,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/translation",
|
"name": "symfony/translation",
|
||||||
"version": "v7.2.2",
|
"version": "v7.2.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/translation.git",
|
"url": "https://github.com/symfony/translation.git",
|
||||||
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923"
|
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923",
|
"url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a",
|
||||||
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923",
|
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -5961,7 +5961,7 @@
|
||||||
"description": "Provides tools to internationalize your application",
|
"description": "Provides tools to internationalize your application",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/translation/tree/v7.2.2"
|
"source": "https://github.com/symfony/translation/tree/v7.2.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -5977,7 +5977,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-12-07T08:18:10+00:00"
|
"time": "2025-02-13T10:27:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/translation-contracts",
|
"name": "symfony/translation-contracts",
|
||||||
|
|
@ -8268,23 +8268,23 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "11.0.8",
|
"version": "11.0.9",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||||
"reference": "418c59fd080954f8c4aa5631d9502ecda2387118"
|
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118",
|
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
|
||||||
"reference": "418c59fd080954f8c4aa5631d9502ecda2387118",
|
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"nikic/php-parser": "^5.3.1",
|
"nikic/php-parser": "^5.4.0",
|
||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"phpunit/php-file-iterator": "^5.1.0",
|
"phpunit/php-file-iterator": "^5.1.0",
|
||||||
"phpunit/php-text-template": "^4.0.1",
|
"phpunit/php-text-template": "^4.0.1",
|
||||||
|
|
@ -8296,7 +8296,7 @@
|
||||||
"theseer/tokenizer": "^1.2.3"
|
"theseer/tokenizer": "^1.2.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^11.5.0"
|
"phpunit/phpunit": "^11.5.2"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-pcov": "PHP extension that provides line coverage",
|
"ext-pcov": "PHP extension that provides line coverage",
|
||||||
|
|
@ -8334,7 +8334,7 @@
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8"
|
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -8342,7 +8342,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-12-11T12:34:27+00:00"
|
"time": "2025-02-25T13:26:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-file-iterator",
|
"name": "phpunit/php-file-iterator",
|
||||||
|
|
|
||||||
|
|
@ -123,4 +123,11 @@ return [
|
||||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'registration' => [
|
||||||
|
'enabled' => env('REGISTRATION_ENABLED', true),
|
||||||
|
],
|
||||||
|
|
||||||
|
'force_https' => env('FORCE_HTTPS', false),
|
||||||
|
'puppeteer_docker' => env('PUPPETEER_DOCKER', false),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,17 @@ return [
|
||||||
'processors' => [PsrLogMessageProcessor::class],
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'stdout' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'handler' => StreamHandler::class,
|
||||||
|
'formatter' => env('LOG_STDOUT_FORMATTER'),
|
||||||
|
'with' => [
|
||||||
|
'stream' => 'php://stdout',
|
||||||
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
|
||||||
'stderr' => [
|
'stderr' => [
|
||||||
'driver' => 'monolog',
|
'driver' => 'monolog',
|
||||||
'level' => env('LOG_LEVEL', 'debug'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,9 @@ return [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'trmnl' => [
|
||||||
|
'proxy_base_url' => env('TRMNL_PROXY_BASE_URL', 'https://trmnl.app'),
|
||||||
|
'proxy_refresh_minutes' => env('TRMNL_PROXY_REFRESH_MINUTES', 15),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?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::table('users', function (Blueprint $table) {
|
||||||
|
$table->boolean('assign_new_devices')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('assign_new_devices');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?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::table('devices', function (Blueprint $table) {
|
||||||
|
$table->boolean('proxy_cloud')->default(false);
|
||||||
|
$table->text('proxy_cloud_response')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('devices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['proxy_cloud', 'proxy_cloud_response']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?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::table('devices', function (Blueprint $table) {
|
||||||
|
$table->json('last_log_request')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('devices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_log_request');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
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\Console\Seeds\WithoutModelEvents;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
|
||||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "4567:80"
|
||||||
|
environment:
|
||||||
|
#- APP_KEY=
|
||||||
|
- TRMNL_PROXY_REFRESH_MINUTES=15
|
||||||
|
# volumes:
|
||||||
|
# - ./database/database.sqlite:/var/www/html/database/database.sqlite
|
||||||
|
# - ./storage:/var/www/html/storage
|
||||||
|
restart: unless-stopped
|
||||||
|
#platform: "linux/arm64/v8"
|
||||||
17
docker/nginx.conf
Normal file
17
docker/nginx.conf
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /var/www/html/public;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
docker/php.ini
Normal file
14
docker/php.ini
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[PHP]
|
||||||
|
memory_limit = 256M
|
||||||
|
max_execution_time = 60
|
||||||
|
upload_max_filesize = 50M
|
||||||
|
post_max_size = 50M
|
||||||
|
|
||||||
|
[opcache]
|
||||||
|
opcache.enable=1
|
||||||
|
opcache.memory_consumption=128
|
||||||
|
opcache.interned_strings_buffer=8
|
||||||
|
opcache.max_accelerated_files=4000
|
||||||
|
opcache.revalidate_freq=60
|
||||||
|
opcache.fast_shutdown=1
|
||||||
|
opcache.enable_cli=1
|
||||||
56
docker/supervisord.conf
Normal file
56
docker/supervisord.conf
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:php-fpm]
|
||||||
|
command=php-fpm
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:nginx]
|
||||||
|
command=nginx -g 'daemon off;'
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:laravel-queue]
|
||||||
|
command=php /var/www/html/artisan queue:work
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
stopwaitsecs=3600
|
||||||
|
|
||||||
|
[program:laravel-scheduler]
|
||||||
|
command=php /var/www/html/artisan schedule:work
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:laravel-setup]
|
||||||
|
command=/bin/sh -c "php /var/www/html/artisan storage:link >> /tmp/storage-link.done"
|
||||||
|
autostart=true
|
||||||
|
autorestart=false
|
||||||
|
startsecs=0
|
||||||
|
exitcodes=0
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
|
||||||
|
[program:laravel-db-migrate]
|
||||||
|
command=/bin/sh -c "php /var/www/html/artisan migrate --force >> /tmp/migrate.done"
|
||||||
|
autostart=true
|
||||||
|
autorestart=false
|
||||||
|
startsecs=0
|
||||||
|
exitcodes=0
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
|
@ -20,15 +20,17 @@
|
||||||
:current="request()->routeIs('devices')">
|
:current="request()->routeIs('devices')">
|
||||||
Devices
|
Devices
|
||||||
</flux:navbar.item>
|
</flux:navbar.item>
|
||||||
|
<flux:navbar.item icon="puzzle-piece" href="{{ route('plugins.index') }}" wire:navigate
|
||||||
|
:current="request()->routeIs(['plugins.index', 'plugins.markup'])">
|
||||||
|
Plugins
|
||||||
|
</flux:navbar.item>
|
||||||
</flux:navbar>
|
</flux:navbar>
|
||||||
|
|
||||||
<flux:spacer/>
|
<flux:spacer/>
|
||||||
|
|
||||||
{{-- <flux:navbar class="mr-1.5 space-x-0.5 py-0!">--}}
|
<flux:navbar class="mr-1.5 space-x-0.5 py-0! max-lg:hidden">
|
||||||
{{-- <flux:tooltip content="Add devices automatically that try to connect to this server" position="bottom">--}}
|
<livewire:actions.device-auto-join/>
|
||||||
{{-- <flux:switch --}}{{-- wire:model.live="device-autojoin" --}}{{-- label="Permit Auto-Join"/>--}}
|
</flux:navbar>
|
||||||
{{-- </flux:tooltip>--}}
|
|
||||||
{{-- </flux:navbar>--}}
|
|
||||||
|
|
||||||
<!-- Desktop User Menu -->
|
<!-- Desktop User Menu -->
|
||||||
<flux:dropdown position="top" align="end">
|
<flux:dropdown position="top" align="end">
|
||||||
|
|
@ -87,22 +89,24 @@
|
||||||
<flux:navlist variant="outline">
|
<flux:navlist variant="outline">
|
||||||
<flux:navlist.group heading="Platform">
|
<flux:navlist.group heading="Platform">
|
||||||
<flux:navlist.item icon="layout-grid" href="{{ route('dashboard') }}" wire:navigate
|
<flux:navlist.item icon="layout-grid" href="{{ route('dashboard') }}" wire:navigate
|
||||||
:current="request()->routeIs('dashboard')">
|
:current="request()->routeIs('dashboard')" class="m-2">
|
||||||
Dashboard
|
Dashboard
|
||||||
</flux:navlist.item>
|
</flux:navlist.item>
|
||||||
<flux:navbar.item icon="square-chart-gantt" href="{{ route('devices') }}" wire:navigate
|
<flux:navbar.item icon="square-chart-gantt" href="{{ route('devices') }}" wire:navigate
|
||||||
:current="request()->routeIs('devices')">
|
:current="request()->routeIs('devices')" class="m-2">
|
||||||
Devices
|
Devices
|
||||||
</flux:navbar.item>
|
</flux:navbar.item>
|
||||||
|
<flux:navbar.item icon="puzzle-piece" href="{{ route('plugins.index') }}" wire:navigate
|
||||||
|
:current="request()->routeIs('plugins.index')" class="m-2">
|
||||||
|
Plugins
|
||||||
|
</flux:navbar.item>
|
||||||
</flux:navlist.group>
|
</flux:navlist.group>
|
||||||
</flux:navlist>
|
</flux:navlist>
|
||||||
|
|
||||||
<flux:spacer/>
|
<flux:spacer/>
|
||||||
|
|
||||||
<flux:navlist variant="outline">
|
<flux:navlist variant="outline">
|
||||||
{{-- <flux:navlist.item icon="folder-git-2" href="https://github.com/laravel/livewire-starter-kit" target="_blank">--}}
|
<livewire:actions.device-auto-join/>
|
||||||
{{-- Repository--}}
|
|
||||||
{{-- </flux:navlist.item>--}}
|
|
||||||
</flux:navlist>
|
</flux:navlist>
|
||||||
</flux:sidebar>
|
</flux:sidebar>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
@props(['percent'])
|
||||||
|
<flux:tooltip content="Battery Percent: {{ $percent }}%" position="bottom">
|
||||||
|
@if ($percent > 60)
|
||||||
|
<flux:icon.battery-full class="dark:text-zinc-200"/>
|
||||||
|
@elseif ($percent < 20)
|
||||||
|
<flux:icon.battery-low class="dark:text-zinc-200"/>
|
||||||
|
@else
|
||||||
|
<flux:icon.battery-medium class="dark:text-zinc-200"/>
|
||||||
|
@endif
|
||||||
|
</flux:tooltip>
|
||||||
12
resources/views/components/responsive-icons/wifi.blade.php
Normal file
12
resources/views/components/responsive-icons/wifi.blade.php
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
@props(['strength', 'rssi'])
|
||||||
|
<flux:tooltip content="Wi-Fi RSSI Level: {{ $rssi }} db" position="bottom">
|
||||||
|
@if ($strength === 3)
|
||||||
|
<flux:icon.wifi class="dark:text-zinc-200"/>
|
||||||
|
@elseif ($strength === 2)
|
||||||
|
<flux:icon.wifi-high class="dark:text-zinc-200"/>
|
||||||
|
@elseif ($strength === 1)
|
||||||
|
<flux:icon.wifi-low class="dark:text-zinc-200"/>
|
||||||
|
@else
|
||||||
|
<flux:icon.wifi-zero class="dark:text-zinc-200"/>
|
||||||
|
@endif
|
||||||
|
</flux:tooltip>
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<x-layouts.app>
|
|
||||||
<div class="bg-muted flex flex-col items-center justify-center gap-6 p-6 md:p-10">
|
|
||||||
<livewire:device-dashboard/>
|
|
||||||
</div>
|
|
||||||
</x-layouts.app>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<x-layouts.app>
|
|
||||||
<livewire:device-manager />
|
|
||||||
</x-layouts.app>
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
<x-layouts.app>
|
|
||||||
<div class="bg-muted flex flex-col items-center justify-center gap-6 p-6 md:p-10">
|
|
||||||
<div class="flex flex-col gap-6">
|
|
||||||
<div
|
|
||||||
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">
|
|
||||||
@php
|
|
||||||
$current_image_uuid =$device->current_screen_image;
|
|
||||||
$current_image_path = 'images/generated/' . $current_image_uuid . '.png';
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<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">Friendly Id: {{$device->friendly_id}}</p>
|
|
||||||
<p class="text-sm dark:text-zinc-400">Refresh Interval: {{$device->default_refresh_interval}}</p>
|
|
||||||
<p class="text-sm dark:text-zinc-400">Battery Voltage: {{$device->last_battery_voltage}}</p>
|
|
||||||
<p class="text-sm dark:text-zinc-400">Wifi RSSI Level: {{$device->last_rssi_level}}</p>
|
|
||||||
<p class="text-sm dark:text-zinc-400">Firmware Version: {{$device->last_firmware_version}}</p>
|
|
||||||
<flux:input.group class="mt-4 mb-2">
|
|
||||||
<flux:input.group.prefix>API Key</flux:input.group.prefix>
|
|
||||||
<flux:input icon="key" value="{{ $device->api_key }}" type="password" viewable class="max-w-xs"/>
|
|
||||||
</flux:input.group>
|
|
||||||
@if($current_image_uuid)
|
|
||||||
<flux:separator class="mt-6 mb-6" text="Current Screen" />
|
|
||||||
<img src="{{ asset($current_image_path) }}" alt="Current Image"/>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-layouts.app>
|
|
||||||
|
|
||||||
|
|
||||||
{{--<x-layouts.app>--}}
|
|
||||||
{{-- <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> <flux:input value="{{ $device->api_key }}" type="password" viewable></flux:input></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-layouts.app>--}}
|
|
||||||
41
resources/views/flux/icon/droplet.blade.php
Normal file
41
resources/views/flux/icon/droplet.blade.php
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{{-- Credit: Lucide (https://lucide.dev) --}}
|
||||||
|
|
||||||
|
@props([
|
||||||
|
'variant' => 'outline',
|
||||||
|
])
|
||||||
|
|
||||||
|
@php
|
||||||
|
if ($variant === 'solid') {
|
||||||
|
throw new \Exception('The "solid" variant is not supported in Lucide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = Flux::classes('shrink-0')
|
||||||
|
->add(match($variant) {
|
||||||
|
'outline' => '[:where(&)]:size-6',
|
||||||
|
'solid' => '[:where(&)]:size-6',
|
||||||
|
'mini' => '[:where(&)]:size-5',
|
||||||
|
'micro' => '[:where(&)]:size-4',
|
||||||
|
});
|
||||||
|
|
||||||
|
$strokeWidth = match ($variant) {
|
||||||
|
'outline' => 2,
|
||||||
|
'mini' => 2.25,
|
||||||
|
'micro' => 2.5,
|
||||||
|
};
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<svg
|
||||||
|
{{ $attributes->class($classes) }}
|
||||||
|
data-flux-icon
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="{{ $strokeWidth }}"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
>
|
||||||
|
<path d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z" />
|
||||||
|
</svg>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div>
|
||||||
|
@if($isFirstUser)
|
||||||
|
<flux:tooltip content="Add devices automatically that try to connect to this server" position="bottom">
|
||||||
|
<flux:switch wire:model.live="deviceAutojoin" label="Permit Auto-Join"/>
|
||||||
|
</flux:tooltip>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
@ -80,7 +80,8 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
||||||
|
|
||||||
<form wire:submit="login" class="flex flex-col gap-6">
|
<form wire:submit="login" class="flex flex-col gap-6">
|
||||||
<!-- Email Address -->
|
<!-- Email Address -->
|
||||||
<flux:input wire:model="email" label="{{ __('Email address') }}" type="email" name="email" required autofocus autocomplete="email" placeholder="email@example.com" />
|
<flux:input wire:model="email" label="{{ __('Email address') }}" type="email" name="email" required autofocus
|
||||||
|
autocomplete="email" placeholder="email@example.com"/>
|
||||||
|
|
||||||
<!-- Password -->
|
<!-- Password -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
|
|
@ -109,8 +110,12 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
@if (Route::has('register'))
|
||||||
<div class="space-x-1 text-center text-sm text-zinc-600 dark:text-zinc-400">
|
<div class="space-x-1 text-center text-sm text-zinc-600 dark:text-zinc-400">
|
||||||
Don't have an account?
|
Don't have an account?
|
||||||
<x-text-link href="{{ route('register') }}">Sign up</x-text-link>
|
<x-text-link href="{{ route('register') }}">Sign up</x-text-link>
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
return view('livewire.device-dashboard', ['devices' => auth()->user()->devices()->paginate(10)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex w-full max-w-3xl flex-col gap-6">
|
<div class="flex w-full max-w-3xl flex-col gap-6">
|
||||||
@if($devices->isEmpty())
|
@if($devices->isEmpty())
|
||||||
|
|
@ -21,7 +33,8 @@
|
||||||
<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;
|
||||||
$current_image_path = 'images/generated/' . $current_image_uuid . '.png';
|
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;
|
||||||
@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>
|
||||||
|
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
<div class="py-12">
|
|
||||||
{{--@dump($devices)--}}
|
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
||||||
<div class="flex justify-between items-center mb-6">
|
|
||||||
<h2 class="text-2xl font-semibold dark:text-gray-100">Devices</h2>
|
|
||||||
<flux:modal.trigger name="create-device">
|
|
||||||
<flux:button icon="plus" variant="primary">Add Device</flux:button>
|
|
||||||
</flux:modal.trigger>
|
|
||||||
</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
|
|
||||||
|
|
||||||
<flux:modal name="create-device" class="md:w-96">
|
|
||||||
<div class="space-y-6">
|
|
||||||
<div>
|
|
||||||
<flux:heading size="lg">Add Device</flux:heading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form wire:submit="createDevice">
|
|
||||||
<div class="mb-4">
|
|
||||||
<flux:input label="Name" wire:model="name" id="name" class="block mt-1 w-full" type="text"
|
|
||||||
name="name"
|
|
||||||
autofocus/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<flux:input label="Mac Address" wire:model="mac_address" id="mac_address"
|
|
||||||
class="block mt-1 w-full"
|
|
||||||
type="text" name="mac_address" autofocus/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<flux:input label="API Key" wire:model="api_key" id="api_key" class="block mt-1 w-full"
|
|
||||||
type="text"
|
|
||||||
name="api_key" autofocus/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<flux:input label="Friendly Id" wire:model="friendly_id" id="friendly_id"
|
|
||||||
class="block mt-1 w-full"
|
|
||||||
type="text" name="friendly_id" autofocus/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<flux:input label="Refresh Rate (seconds)" wire:model="default_refresh_interval"
|
|
||||||
id="default_refresh_interval"
|
|
||||||
class="block mt-1 w-full" type="text" name="default_refresh_interval"
|
|
||||||
autofocus/>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<flux:spacer/>
|
|
||||||
<flux:button type="submit" variant="primary">Create Device</flux:button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</flux:modal>
|
|
||||||
|
|
||||||
<table
|
|
||||||
class="min-w-full table-fixed text-zinc-800 divide-y divide-zinc-800/10 dark:divide-white/20 text-zinc-800"
|
|
||||||
data-flux-table="">
|
|
||||||
<thead data-flux-columns="">
|
|
||||||
<tr>
|
|
||||||
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
|
||||||
data-flux-column="">
|
|
||||||
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Name</div>
|
|
||||||
</th>
|
|
||||||
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
|
||||||
data-flux-column="">
|
|
||||||
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Friendly ID</div>
|
|
||||||
</th>
|
|
||||||
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
|
||||||
data-flux-column="">
|
|
||||||
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Mac Address</div>
|
|
||||||
</th>
|
|
||||||
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
|
||||||
data-flux-column="">
|
|
||||||
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Refresh</div>
|
|
||||||
</th>
|
|
||||||
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
|
||||||
data-flux-column="">
|
|
||||||
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Actions</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody class="divide-y divide-zinc-800/10 dark:divide-white/20" data-flux-rows="">
|
|
||||||
@foreach ($devices as $device)
|
|
||||||
<tr data-flux-row="">
|
|
||||||
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
|
||||||
>
|
|
||||||
{{ $device->name }}
|
|
||||||
</td>
|
|
||||||
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
|
||||||
>
|
|
||||||
{{ $device->friendly_id }}
|
|
||||||
</td>
|
|
||||||
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
|
||||||
>
|
|
||||||
<div type="button" data-flux-badge="data-flux-badge"
|
|
||||||
class="inline-flex items-center font-medium whitespace-nowrap -mt-1 -mb-1 text-xs py-1 [&_[data-flux-badge-icon]]:size-3 [&_[data-flux-badge-icon]]:mr-1 rounded-md px-2 text-zinc-700 [&_button]:!text-zinc-700 dark:text-zinc-200 [&_button]:dark:!text-zinc-200 bg-zinc-400/15 dark:bg-zinc-400/40 [&:is(button)]:hover:bg-zinc-400/25 [&:is(button)]:hover:dark:bg-zinc-400/50">
|
|
||||||
{{ $device->mac_address }}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
|
||||||
>
|
|
||||||
{{ $device->default_refresh_interval }}
|
|
||||||
</td>
|
|
||||||
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap font-medium text-zinc-800 dark:text-white"
|
|
||||||
>
|
|
||||||
<flux:button href="{{ route('devices.configure', $device) }}" wire:navigate icon="eye">
|
|
||||||
</flux:button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
<!--[if ENDBLOCK]><![endif]-->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
120
resources/views/livewire/devices/configure.blade.php
Normal file
120
resources/views/livewire/devices/configure.blade.php
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
|
||||||
|
public $device;
|
||||||
|
|
||||||
|
public function mount(\App\Models\Device $device)
|
||||||
|
{
|
||||||
|
abort_unless(auth()->user()->devices->contains($device), 403);
|
||||||
|
|
||||||
|
$current_image_uuid = $device->current_screen_image;
|
||||||
|
$current_image_path = 'images/generated/' . $current_image_uuid . '.png';
|
||||||
|
|
||||||
|
return view('livewire.devices.configure', compact('device'), [
|
||||||
|
'image' => ($current_image_uuid) ? url($current_image_path) : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteDevice(\App\Models\Device $device)
|
||||||
|
{
|
||||||
|
abort_unless(auth()->user()->devices->contains($device), 403);
|
||||||
|
$device->delete();
|
||||||
|
|
||||||
|
redirect()->route('devices');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="bg-muted flex flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<div
|
||||||
|
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 min-w-lg">
|
||||||
|
@php
|
||||||
|
$current_image_uuid =$device->current_screen_image;
|
||||||
|
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;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<flux:tooltip content="Friendly ID: {{$device->friendly_id}}" position="bottom">
|
||||||
|
<h1 class="text-xl font-medium dark:text-zinc-200">{{ $device->name }}</h1>
|
||||||
|
</flux:tooltip>
|
||||||
|
<div>
|
||||||
|
<flux:modal.trigger name="edit-device">
|
||||||
|
<flux:button icon="key" variant="subtle"/>
|
||||||
|
</flux:modal.trigger>
|
||||||
|
<flux:modal.trigger name="delete-device">
|
||||||
|
<flux:button icon="trash" variant="danger"/>
|
||||||
|
</flux:modal.trigger>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<flux:tooltip content="Last update" position="bottom">
|
||||||
|
<span class="dark:text-zinc-200">{{$device->updated_at->diffForHumans()}}</span>
|
||||||
|
</flux:tooltip>
|
||||||
|
<flux:separator vertical/>
|
||||||
|
<flux:tooltip content="MAC Address" position="bottom">
|
||||||
|
<span class="dark:text-zinc-200">{{$device->mac_address}}</span>
|
||||||
|
</flux:tooltip>
|
||||||
|
<flux:separator vertical/>
|
||||||
|
<flux:tooltip content="Firmware Version" position="bottom">
|
||||||
|
<span class="dark:text-zinc-200">{{$device->last_firmware_version}}</span>
|
||||||
|
</flux:tooltip>
|
||||||
|
<flux:separator vertical/>
|
||||||
|
<x-responsive-icons.wifi :strength="$device->wifiStrengh" :rssi="$device->last_rssi_level"
|
||||||
|
class="dark:text-zinc-200"/>
|
||||||
|
<flux:separator vertical/>
|
||||||
|
<x-responsive-icons.battery :percent="$device->batteryPercent"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<flux:modal name="edit-device" class="md:w-96">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
{{-- <flux:heading size="lg">Edit TRMNL</flux:heading>--}}
|
||||||
|
{{-- <flux:subheading></flux:subheading>--}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- <flux:input label="Name" value="{{ $device->name }}"/>--}}
|
||||||
|
|
||||||
|
<flux:input label="API Key" icon="key" value="{{ $device->api_key }}" type="password"
|
||||||
|
viewable class="max-w-xs"/>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<flux:spacer/>
|
||||||
|
|
||||||
|
{{-- <flux:button type="submit" variant="primary">Save changes</flux:button>--}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</flux:modal>
|
||||||
|
|
||||||
|
<flux:modal name="delete-device" class="min-w-[22rem] space-y-6">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="lg">Delete {{$device->name}}?</flux:heading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<flux:spacer/>
|
||||||
|
|
||||||
|
<flux:modal.close>
|
||||||
|
<flux:button variant="ghost">Cancel</flux:button>
|
||||||
|
</flux:modal.close>
|
||||||
|
<flux:button wire:click="deleteDevice({{ $device->id }})" variant="danger">Delete device</flux:button>
|
||||||
|
</div>
|
||||||
|
</flux:modal>
|
||||||
|
|
||||||
|
|
||||||
|
@if($current_image_uuid)
|
||||||
|
<flux:separator class="mt-6 mb-6" text="Next Screen"/>
|
||||||
|
<img src="{{ asset($current_image_path) }}" alt="Next Image"/>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
205
resources/views/livewire/devices/manage.blade.php
Normal file
205
resources/views/livewire/devices/manage.blade.php
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
|
||||||
|
public $devices;
|
||||||
|
|
||||||
|
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 mount()
|
||||||
|
{
|
||||||
|
$this->devices = auth()->user()->devices;
|
||||||
|
return view('livewire.devices.manage');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createDevice(): void
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
Device::create([
|
||||||
|
'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(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->reset();
|
||||||
|
\Flux::modal('create-device')->close();
|
||||||
|
|
||||||
|
$this->devices = auth()->user()->devices;
|
||||||
|
session()->flash('message', 'Device created successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toggleProxyCloud(Device $device): void
|
||||||
|
{
|
||||||
|
abort_unless(auth()->user()->devices->contains($device), 403);
|
||||||
|
$device->update([
|
||||||
|
'proxy_cloud' => !$device->proxy_cloud,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// if ($device->proxy_cloud) {
|
||||||
|
// \App\Jobs\FetchProxyCloudResponses::dispatch();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="py-12">
|
||||||
|
{{--@dump($devices)--}}
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-2xl font-semibold dark:text-gray-100">Devices</h2>
|
||||||
|
<flux:modal.trigger name="create-device">
|
||||||
|
<flux:button icon="plus" variant="primary">Add Device</flux:button>
|
||||||
|
</flux:modal.trigger>
|
||||||
|
</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
|
||||||
|
|
||||||
|
<flux:modal name="create-device" class="md:w-96">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="lg">Add Device</flux:heading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form wire:submit="createDevice">
|
||||||
|
<div class="mb-4">
|
||||||
|
<flux:input label="Name" wire:model="name" id="name" class="block mt-1 w-full" type="text"
|
||||||
|
name="name"
|
||||||
|
autofocus/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<flux:input label="Mac Address" wire:model="mac_address" id="mac_address"
|
||||||
|
class="block mt-1 w-full"
|
||||||
|
type="text" name="mac_address" autofocus/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<flux:input label="API Key" wire:model="api_key" id="api_key" class="block mt-1 w-full"
|
||||||
|
type="text"
|
||||||
|
name="api_key" autofocus/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<flux:input label="Friendly Id" wire:model="friendly_id" id="friendly_id"
|
||||||
|
class="block mt-1 w-full"
|
||||||
|
type="text" name="friendly_id" autofocus/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<flux:input label="Refresh Rate (seconds)" wire:model="default_refresh_interval"
|
||||||
|
id="default_refresh_interval"
|
||||||
|
class="block mt-1 w-full" type="text" name="default_refresh_interval"
|
||||||
|
autofocus/>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<flux:spacer/>
|
||||||
|
<flux:button type="submit" variant="primary">Create Device</flux:button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</flux:modal>
|
||||||
|
|
||||||
|
<table
|
||||||
|
class="min-w-full table-fixed text-zinc-800 divide-y divide-zinc-800/10 dark:divide-white/20 text-zinc-800"
|
||||||
|
data-flux-table="">
|
||||||
|
<thead data-flux-columns="">
|
||||||
|
<tr>
|
||||||
|
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
||||||
|
data-flux-column="">
|
||||||
|
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Name</div>
|
||||||
|
</th>
|
||||||
|
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
||||||
|
data-flux-column="">
|
||||||
|
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Friendly ID</div>
|
||||||
|
</th>
|
||||||
|
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
||||||
|
data-flux-column="">
|
||||||
|
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Mac Address</div>
|
||||||
|
</th>
|
||||||
|
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
||||||
|
data-flux-column="">
|
||||||
|
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Refresh</div>
|
||||||
|
</th>
|
||||||
|
<th class="py-3 px-3 first:pl-0 last:pr-0 text-left text-sm font-medium text-zinc-800 dark:text-white"
|
||||||
|
data-flux-column="">
|
||||||
|
<div class="whitespace-nowrap flex group-[]/right-align:justify-end">Actions</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody class="divide-y divide-zinc-800/10 dark:divide-white/20" data-flux-rows="">
|
||||||
|
@foreach ($devices as $device)
|
||||||
|
<tr data-flux-row="">
|
||||||
|
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
||||||
|
>
|
||||||
|
{{ $device->name }}
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
||||||
|
>
|
||||||
|
{{ $device->friendly_id }}
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
||||||
|
>
|
||||||
|
<div type="button" data-flux-badge="data-flux-badge"
|
||||||
|
class="inline-flex items-center font-medium whitespace-nowrap -mt-1 -mb-1 text-xs py-1 [&_[data-flux-badge-icon]]:size-3 [&_[data-flux-badge-icon]]:mr-1 rounded-md px-2 text-zinc-700 [&_button]:!text-zinc-700 dark:text-zinc-200 [&_button]:dark:!text-zinc-200 bg-zinc-400/15 dark:bg-zinc-400/40 [&:is(button)]:hover:bg-zinc-400/25 [&:is(button)]:hover:dark:bg-zinc-400/50">
|
||||||
|
{{ $device->mac_address }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap text-zinc-500 dark:text-zinc-300"
|
||||||
|
>
|
||||||
|
{{ $device->default_refresh_interval }}
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3 first:pl-0 last:pr-0 text-sm whitespace-nowrap font-medium text-zinc-800 dark:text-white"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<flux:button href="{{ route('devices.configure', $device) }}" wire:navigate icon="eye">
|
||||||
|
</flux:button>
|
||||||
|
|
||||||
|
<flux:tooltip
|
||||||
|
content="Proxies images from the TRMNL Cloud service when no image is set (available in TRMNL DEV Edition only)."
|
||||||
|
position="bottom">
|
||||||
|
<flux:switch wire:model.live="proxy_cloud"
|
||||||
|
wire:click="toggleProxyCloud({{ $device->id }})"
|
||||||
|
:checked="$device->proxy_cloud" label="☁️ Proxy"/>
|
||||||
|
</flux:tooltip>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<!--[if ENDBLOCK]><![endif]-->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
56
resources/views/livewire/plugins/api.blade.php
Normal file
56
resources/views/livewire/plugins/api.blade.php
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
public $token;
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$token = Auth::user()?->tokens()?->first();
|
||||||
|
if ($token === null) {
|
||||||
|
$token = Auth::user()->createToken('api-token', ['update-screen']);
|
||||||
|
}
|
||||||
|
$this->token = $token->plainTextToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function regenerateToken()
|
||||||
|
{
|
||||||
|
Auth::user()->tokens()?->first()?->delete();
|
||||||
|
$token = Auth::user()->createToken('api-token', ['update-screen']);
|
||||||
|
$this->token = $token->plainTextToken;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="py-12">
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-2xl font-semibold dark:text-gray-100">API</h2>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<flux:badge>POST</flux:badge>
|
||||||
|
<span class="ml-2 font-mono">{{route('display.update')}}</span>
|
||||||
|
</p>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-lg">Headers</h3>
|
||||||
|
<div>Authorization <span class="ml-2 font-mono">Bearer {{$token ?? '**********'}}</span>
|
||||||
|
<flux:button variant="subtle" size="xs" class="mt-2" wire:click="regenerateToken()">
|
||||||
|
Regenerate Token
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-lg">Body</h3>
|
||||||
|
<div class="font-mono">
|
||||||
|
<pre>
|
||||||
|
{"markup":"<h1>Hello World</h1>"}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
36
resources/views/livewire/plugins/index.blade.php
Normal file
36
resources/views/livewire/plugins/index.blade.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
|
||||||
|
public $plugins = [
|
||||||
|
'markup' =>
|
||||||
|
['name' => 'Markup', 'icon' => 'code-backet', 'route' => 'plugins.markup'],
|
||||||
|
'api' =>
|
||||||
|
['name' => 'API', 'icon' => 'code-backet', 'route' => 'plugins.api'],
|
||||||
|
];
|
||||||
|
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="py-12">
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-2xl font-semibold dark:text-gray-100">Plugins</h2>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||||
|
@foreach($plugins as $plugin)
|
||||||
|
<div
|
||||||
|
class="rounded-xl border bg-white dark:bg-stone-950 dark:border-stone-800 text-stone-800 shadow-xs">
|
||||||
|
<a href="{{ route($plugin['route']) }}" class="block">
|
||||||
|
<div class="flex items-center space-x-4 px-10 py-8">
|
||||||
|
<flux:icon name="code-bracket" class="text-4xl text-accent"/>
|
||||||
|
<h3 class="text-xl font-medium dark:text-zinc-200">{{$plugin['name']}}</h3>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
171
resources/views/livewire/plugins/markup.blade.php
Normal file
171
resources/views/livewire/plugins/markup.blade.php
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\GenerateScreenJob;
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
|
||||||
|
public string $blade_code = '';
|
||||||
|
public bool $isLoading = false;
|
||||||
|
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
$this->isLoading = true;
|
||||||
|
|
||||||
|
$this->validate([
|
||||||
|
'blade_code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rendered = Blade::render($this->blade_code);
|
||||||
|
|
||||||
|
// if (config('app.puppeteer_docker')) {
|
||||||
|
// GenerateScreenJob::dispatch(auth()->user()->devices()->first()->id, $rendered);
|
||||||
|
// } else {
|
||||||
|
GenerateScreenJob::dispatchSync(auth()->user()->devices()->first()->id, $rendered);
|
||||||
|
// }
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('error', $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderExample(string $example)
|
||||||
|
{
|
||||||
|
switch ($example) {
|
||||||
|
case 'quote':
|
||||||
|
$markup = $this->renderQuote();
|
||||||
|
break;
|
||||||
|
case 'trainMonitor':
|
||||||
|
$markup = $this->renderTrainMonitor();
|
||||||
|
break;
|
||||||
|
case 'homeAssistant':
|
||||||
|
$markup = $this->renderHomeAssistant();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$markup = '<h1>Hello World!</h1>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$this->blade_code = $markup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderQuote(): string
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<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>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderTrainMonitor()
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<x-trmnl::view>
|
||||||
|
<x-trmnl::layout>
|
||||||
|
<x-trmnl::table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><x-trmnl::title>Abfahrt</x-trmnl::title></th>
|
||||||
|
<th><x-trmnl::title>Aktuell</x-trmnl::title></th>
|
||||||
|
<th><x-trmnl::title>Zug</x-trmnl::title></th>
|
||||||
|
<th><x-trmnl::title>Ziel</x-trmnl::title></th>
|
||||||
|
<th><x-trmnl::title>Steig</x-trmnl::title></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><x-trmnl::label>08:51</x-trmnl::label></td>
|
||||||
|
<td><x-trmnl::label>08:52</x-trmnl::label></td>
|
||||||
|
<td><x-trmnl::label>REX 1</x-trmnl::label></td>
|
||||||
|
<td><x-trmnl::label>Vienna Main Station</x-trmnl::label></td>
|
||||||
|
<td><x-trmnl::label>3</x-trmnl::label></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</x-trmnl::table>
|
||||||
|
</x-trmnl::layout>
|
||||||
|
<x-trmnl::title-bar title="Train Monitor"/>
|
||||||
|
</x-trmnl::view>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderHomeAssistant()
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<x-trmnl::view>
|
||||||
|
<x-trmnl::layout class="layout--col gap--space-between">
|
||||||
|
<x-trmnl::grid cols="4">
|
||||||
|
<x-trmnl::col position="center">
|
||||||
|
<x-trmnl::item>
|
||||||
|
<x-trmnl::meta/>
|
||||||
|
<x-trmnl::content>
|
||||||
|
<x-trmnl::value size="large">23.3°</x-trmnl::value>
|
||||||
|
<x-trmnl::label class="w--full flex">
|
||||||
|
<flux:icon icon="droplet"/>
|
||||||
|
47.52 %
|
||||||
|
</x-trmnl::label>
|
||||||
|
<x-trmnl::label class="w--full flex">Sensor 1</x-trmnl::label>
|
||||||
|
</x-trmnl::content>
|
||||||
|
</x-trmnl::item>
|
||||||
|
</x-trmnl::col>
|
||||||
|
</x-trmnl::grid>
|
||||||
|
</x-trmnl::layout>
|
||||||
|
<x-trmnl::title-bar title="Home Assistant"/>
|
||||||
|
</x-trmnl::view>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="py-12">
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
|
<h2 class="text-2xl font-semibold dark:text-gray-100">Markup</h2>
|
||||||
|
|
||||||
|
{{-- <div class="flex justify-between items-center mb-6">--}}
|
||||||
|
|
||||||
|
<div class="mt-5 mb-5 ">
|
||||||
|
<span>Examples</span>
|
||||||
|
<div class="text-accent">
|
||||||
|
<a href="#" wire:click="renderExample('quote')" class="text-xl">Quote</a> |
|
||||||
|
<a href="#" wire:click="renderExample('trainMonitor')" class="text-xl">Train Monitor</a> |
|
||||||
|
<a href="#" wire:click="renderExample('homeAssistant')" class="text-xl">Temperature Sensors</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form wire:submit="submit">
|
||||||
|
<div class="mb-4">
|
||||||
|
<flux:textarea
|
||||||
|
label="Blade Code"
|
||||||
|
class="font-mono"
|
||||||
|
wire:model="blade_code"
|
||||||
|
id="blade_code"
|
||||||
|
name="blade_code"
|
||||||
|
rows="15"
|
||||||
|
placeholder="Enter your blade code here..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<flux:spacer/>
|
||||||
|
<flux:button type="submit" variant="primary">
|
||||||
|
Generate Screen
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{-- </div>--}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -1,22 +1,40 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\GenerateScreenJob;
|
||||||
use App\Models\Device;
|
use App\Models\Device;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
Route::get('/display', function (Request $request) {
|
Route::get('/display', function (Request $request) {
|
||||||
|
|
||||||
$mac_address = $request->header('id');
|
$mac_address = $request->header('id');
|
||||||
$access_token = $request->header('access-token');
|
$access_token = $request->header('access-token');
|
||||||
|
|
||||||
$device = Device::where('mac_address', $mac_address)
|
$device = Device::where('mac_address', $mac_address)
|
||||||
->where('api_key', $access_token)
|
->where('api_key', $access_token)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $device) {
|
if (! $device) {
|
||||||
|
// Check if there's a user with assign_new_devices enabled
|
||||||
|
$auto_assign_user = User::where('assign_new_devices', true)->first();
|
||||||
|
|
||||||
|
if ($auto_assign_user) {
|
||||||
|
// Create a new device and assign it to this user
|
||||||
|
$device = Device::create([
|
||||||
|
'mac_address' => $mac_address,
|
||||||
|
'api_key' => $access_token,
|
||||||
|
'user_id' => $auto_assign_user->id,
|
||||||
|
'name' => "{$auto_assign_user->name}'s TRMNL",
|
||||||
|
'friendly_id' => Str::random(6),
|
||||||
|
'default_refresh_interval' => 900,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'MAC Address not registered or invalid access token',
|
'message' => 'MAC Address not registered or invalid access token',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$device->update([
|
$device->update([
|
||||||
'last_rssi_level' => $request->header('rssi'),
|
'last_rssi_level' => $request->header('rssi'),
|
||||||
|
|
@ -25,9 +43,13 @@ Route::get('/display', function (Request $request) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$image_uuid = $device->current_screen_image;
|
$image_uuid = $device->current_screen_image;
|
||||||
|
if (! $image_uuid) {
|
||||||
|
$image_path = 'images/setup-logo.bmp';
|
||||||
|
$filename = 'setup-logo.bmp';
|
||||||
|
} else {
|
||||||
$image_path = 'images/generated/'.$image_uuid.'.bmp';
|
$image_path = 'images/generated/'.$image_uuid.'.bmp';
|
||||||
$filename = basename($image_path);
|
$filename = basename($image_path);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => '0',
|
'status' => '0',
|
||||||
|
|
@ -67,8 +89,24 @@ Route::get('/setup', function (Request $request) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::post('/log', function (Request $request) {
|
Route::post('/log', function (Request $request) {
|
||||||
$logs = $request->json('log.logs_array', []);
|
$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' => 'Device not found or invalid access token',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$device->update([
|
||||||
|
'last_log_request' => $request->json()->all(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$logs = $request->json('log.logs_array', []);
|
||||||
foreach ($logs as $log) {
|
foreach ($logs as $log) {
|
||||||
\Log::info('Device Log', $log);
|
\Log::info('Device Log', $log);
|
||||||
}
|
}
|
||||||
|
|
@ -81,3 +119,23 @@ Route::post('/log', function (Request $request) {
|
||||||
Route::get('/user', function (Request $request) {
|
Route::get('/user', function (Request $request) {
|
||||||
return $request->user();
|
return $request->user();
|
||||||
})->middleware('auth:sanctum');
|
})->middleware('auth:sanctum');
|
||||||
|
|
||||||
|
Route::post('/display/update', function (Request $request) {
|
||||||
|
$request->validate([
|
||||||
|
'device_id' => 'required|exists:devices,id',
|
||||||
|
'markup' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$deviceId = $request['device_id'];
|
||||||
|
abort_unless($request->user()->devices->contains($deviceId), 403);
|
||||||
|
|
||||||
|
$view = Blade::render($request['markup']);
|
||||||
|
|
||||||
|
GenerateScreenJob::dispatchSync($deviceId, $view);
|
||||||
|
|
||||||
|
response()->json([
|
||||||
|
'message' => 'success',
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
->name('display.update')
|
||||||
|
->middleware('auth:sanctum', 'ability:update-screen');
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@ Route::middleware('guest')->group(function () {
|
||||||
Volt::route('login', 'auth.login')
|
Volt::route('login', 'auth.login')
|
||||||
->name('login');
|
->name('login');
|
||||||
|
|
||||||
|
if (config('app.registration.enabled')) {
|
||||||
Volt::route('register', 'auth.register')
|
Volt::route('register', 'auth.register')
|
||||||
->name('register');
|
->name('register');
|
||||||
|
}
|
||||||
|
|
||||||
Volt::route('forgot-password', 'auth.forgot-password')
|
Volt::route('forgot-password', 'auth.forgot-password')
|
||||||
->name('password.request');
|
->name('password.request');
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Foundation\Inspiring;
|
use App\Jobs\FetchProxyCloudResponses;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
|
|
||||||
// Artisan::command('inspire', function () {
|
// Artisan::command('inspire', function () {
|
||||||
// $this->comment(Inspiring::quote());
|
// $this->comment(Inspiring::quote());
|
||||||
// })->purpose('Display an inspiring quote')->hourly();
|
// })->purpose('Display an inspiring quote')->hourly();
|
||||||
|
|
||||||
|
Schedule::job(new FetchProxyCloudResponses)->everyFifteenMinutes();
|
||||||
|
|
|
||||||
|
|
@ -7,30 +7,21 @@ Route::get('/', function () {
|
||||||
return view('welcome');
|
return view('welcome');
|
||||||
})->name('home');
|
})->name('home');
|
||||||
|
|
||||||
Route::view('dashboard', 'dashboard')
|
|
||||||
->middleware(['auth', 'verified'])
|
|
||||||
->name('dashboard');
|
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
Route::redirect('settings', 'settings/profile');
|
Route::redirect('settings', 'settings/profile');
|
||||||
|
|
||||||
Volt::route('settings/profile', 'settings.profile')->name('settings.profile');
|
Volt::route('settings/profile', 'settings.profile')->name('settings.profile');
|
||||||
Volt::route('settings/password', 'settings.password')->name('settings.password');
|
Volt::route('settings/password', 'settings.password')->name('settings.password');
|
||||||
Volt::route('settings/appearance', 'settings.appearance')->name('settings.appearance');
|
Volt::route('settings/appearance', 'settings.appearance')->name('settings.appearance');
|
||||||
|
|
||||||
Route::get('/devices', function () {
|
Volt::route('/dashboard', 'device-dashboard')->name('dashboard');
|
||||||
return view('devices');
|
|
||||||
})->name('devices');
|
|
||||||
|
|
||||||
Route::get('/devices/{device}/configure', function (App\Models\Device $device) {
|
Volt::route('/devices', 'devices.manage')->name('devices');
|
||||||
$current_image_uuid = auth()->user()->devices()->find($device->id)->current_screen_image;
|
Volt::route('/devices/{device}/configure', 'devices.configure')->name('devices.configure');
|
||||||
$current_image_path = 'images/generated/' . $current_image_uuid . '.png';
|
|
||||||
|
|
||||||
return view('devices.configure', compact('device'), [
|
Volt::route('plugins', 'plugins.index')->name('plugins.index');
|
||||||
'image' => ($current_image_uuid) ? url($current_image_path) : null,
|
|
||||||
]);
|
|
||||||
})->name('devices.configure');
|
|
||||||
|
|
||||||
|
Volt::route('plugins/markup', 'plugins.markup')->name('plugins.markup');
|
||||||
|
Volt::route('plugins/api', 'plugins.api')->name('plugins.api');
|
||||||
});
|
});
|
||||||
|
|
||||||
require __DIR__.'/auth.php';
|
require __DIR__.'/auth.php';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue