Initial commit device-management
tests / ci (push) Has been cancelled

This commit is contained in:
Wian Drs
2026-08-25 09:42:00 +07:00
commit 1e80be180e
1159 changed files with 149545 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Models\Concerns;
use App\Models\Tenant;
use App\Support\TenantContext;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
trait BelongsToTenant
{
protected static function bootBelongsToTenant(): void
{
static::addGlobalScope('tenant', function (Builder $builder): void {
$tenantId = app(TenantContext::class)->id();
if ($tenantId !== null) {
$builder->where($builder->qualifyColumn('tenant_id'), $tenantId);
}
});
static::creating(function (self $model): void {
$tenantId = app(TenantContext::class)->id();
if ($tenantId !== null && $model->getAttribute('tenant_id') === null) {
$model->setAttribute('tenant_id', $tenantId);
}
});
}
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Enums\DeploymentMode;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class DeploymentInstallation extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'name', 'deployment_type', 'domain', 'instance_key_hash', 'fingerprint_hash', 'status', 'activated_at', 'last_seen_at'];
protected static function booted(): void
{
static::creating(fn (self $installation) => $installation->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['deployment_type' => DeploymentMode::class, 'activated_at' => 'immutable_datetime', 'last_seen_at' => 'immutable_datetime'];
}
public function licenses(): HasMany
{
return $this->hasMany(License::class);
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Device extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'name', 'hostname', 'device_vendor_id', 'device_type_id', 'device_model_id', 'serial_number', 'management_address', 'connection_type', 'management_port', 'connection_ports', 'location', 'latitude', 'longitude', 'firmware_version', 'software_version', 'status', 'last_seen_at', 'notes', 'is_active', 'created_by', 'updated_by', 'activation_status', 'activated_at', 'activation_message', 'remove_legacy_users_on_activation', 'device_facts', 'base_sync_status', 'base_sync_error_code', 'base_sync_message', 'base_synced_at', 'base_sync_attempted_at'];
protected $hidden = ['credentials'];
protected static function booted(): void
{
static::creating(fn (Device $device) => $device->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['is_active' => 'boolean', 'remove_legacy_users_on_activation' => 'boolean', 'connection_ports' => 'array', 'device_facts' => 'array', 'last_seen_at' => 'datetime', 'activated_at' => 'datetime', 'base_synced_at' => 'datetime', 'base_sync_attempted_at' => 'datetime', 'latitude' => 'decimal:7', 'longitude' => 'decimal:7'];
}
public function portFor(string $protocol): ?int
{
$port = $this->connection_ports[$protocol] ?? ($this->connection_type === $protocol ? $this->management_port : null);
return $port ? (int) $port : null;
}
public function hasConnection(string $protocol): bool
{
return $this->portFor($protocol) !== null;
}
public function vendor()
{
return $this->belongsTo(DeviceVendor::class, 'device_vendor_id');
}
public function type()
{
return $this->belongsTo(DeviceType::class, 'device_type_id');
}
public function model()
{
return $this->belongsTo(DeviceModel::class, 'device_model_id');
}
public function credentials()
{
return $this->hasMany(DeviceCredential::class);
}
public function creator()
{
return $this->belongsTo(User::class, 'created_by');
}
public function userAssignments()
{
return $this->hasMany(DeviceUserAssignment::class);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use App\Casts\TenantEncrypted;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
class DeviceAccessUser extends Model
{
use BelongsToTenant, SoftDeletes;
protected $fillable = ['tenant_id', 'display_name', 'username', 'password', 'is_enabled', 'notes', 'created_by', 'updated_by'];
protected $hidden = ['password'];
protected static function booted(): void
{
static::creating(fn (self $user) => $user->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['password' => TenantEncrypted::class, 'is_enabled' => 'boolean'];
}
public function assignments()
{
return $this->hasMany(DeviceUserAssignment::class);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use App\Casts\TenantEncrypted;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class DeviceCredential extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'device_id', 'name', 'username', 'password', 'enable_password', 'connection_type', 'privilege_type', 'is_master', 'is_active', 'last_verified_at', 'created_by', 'updated_by'];
protected $hidden = ['password', 'enable_password'];
protected static function booted(): void
{
static::creating(fn (DeviceCredential $credential) => $credential->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['password' => TenantEncrypted::class, 'enable_password' => TenantEncrypted::class, 'is_master' => 'boolean', 'is_active' => 'boolean', 'last_verified_at' => 'datetime'];
}
public function device()
{
return $this->belongsTo(Device::class);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DeviceModel extends Model
{
protected $fillable = ['device_vendor_id', 'device_type_id', 'name', 'model_code', 'description', 'is_active'];
protected function casts(): array
{
return ['is_active' => 'boolean'];
}
public function vendor()
{
return $this->belongsTo(DeviceVendor::class, 'device_vendor_id');
}
public function type()
{
return $this->belongsTo(DeviceType::class, 'device_type_id');
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DeviceType extends Model
{
protected $fillable = ['name', 'slug', 'is_active'];
protected function casts(): array
{
return ['is_active' => 'boolean'];
}
public function models()
{
return $this->hasMany(DeviceModel::class);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class DeviceUserAssignment extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'device_access_user_id', 'device_id', 'group_name', 'desired_operation', 'sync_status', 'remote_id', 'remote_username', 'error_code', 'message', 'attempts', 'last_attempted_at', 'last_synced_at'];
protected static function booted(): void
{
static::creating(fn (self $assignment) => $assignment->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['last_attempted_at' => 'datetime', 'last_synced_at' => 'datetime'];
}
public function accessUser()
{
return $this->belongsTo(DeviceAccessUser::class, 'device_access_user_id')->withTrashed();
}
public function device()
{
return $this->belongsTo(Device::class);
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DeviceVendor extends Model
{
protected $fillable = ['name', 'slug', 'description', 'is_active'];
protected function casts(): array
{
return ['is_active' => 'boolean'];
}
public function models()
{
return $this->hasMany(DeviceModel::class);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Models;
use App\Enums\LicenseStatus;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class License extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'deployment_installation_id', 'license_key_hash', 'plan', 'status', 'max_devices', 'max_users', 'features', 'signed_payload', 'starts_at', 'expires_at', 'grace_until', 'issued_by'];
protected static function booted(): void
{
static::creating(fn (self $license) => $license->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['status' => LicenseStatus::class, 'features' => 'array', 'starts_at' => 'immutable_datetime', 'expires_at' => 'immutable_datetime', 'grace_until' => 'immutable_datetime'];
}
public function installation(): BelongsTo
{
return $this->belongsTo(DeploymentInstallation::class, 'deployment_installation_id');
}
public function issuer(): BelongsTo
{
return $this->belongsTo(User::class, 'issued_by');
}
public function events(): HasMany
{
return $this->hasMany(LicenseEvent::class);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class LicenseEvent extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'license_id', 'deployment_installation_id', 'actor_id', 'event_type', 'metadata', 'occurred_at'];
protected static function booted(): void
{
static::creating(fn (self $event) => $event->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['metadata' => 'array', 'occurred_at' => 'immutable_datetime'];
}
public function license(): BelongsTo
{
return $this->belongsTo(License::class);
}
public function installation(): BelongsTo
{
return $this->belongsTo(DeploymentInstallation::class, 'deployment_installation_id');
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
use Spatie\Permission\Models\Role;
class Tenant extends Model
{
use HasFactory;
protected $fillable = ['name', 'slug', 'is_active'];
protected static function booted(): void
{
static::creating(function (Tenant $tenant): void {
$tenant->uuid ??= (string) Str::uuid();
});
}
protected function casts(): array
{
return ['is_active' => 'boolean'];
}
public function users(): HasMany
{
return $this->hasMany(User::class);
}
public function roles(): HasMany
{
return $this->hasMany(Role::class);
}
public function installations(): HasMany
{
return $this->hasMany(DeploymentInstallation::class);
}
public function licenses(): HasMany
{
return $this->hasMany(License::class);
}
public function devices(): HasMany
{
return $this->hasMany(Device::class);
}
public function devicePolicy()
{
return $this->hasOne(TenantDevicePolicy::class);
}
public function deviceSetting()
{
return $this->hasOne(TenantDeviceSetting::class);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
class TenantDevicePolicy extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'tenant_user_can_create', 'tenant_user_can_update_own', 'tenant_user_can_delete_own'];
protected function casts(): array
{
return ['tenant_user_can_create' => 'boolean', 'tenant_user_can_update_own' => 'boolean', 'tenant_user_can_delete_own' => 'boolean'];
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use App\Casts\TenantEncrypted;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
class TenantDeviceSetting extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'base_username', 'base_password', 'use_tls', 'verify_tls', 'connection_timeout'];
protected $hidden = ['base_password'];
protected function casts(): array
{
return ['base_password' => TenantEncrypted::class, 'use_tls' => 'boolean', 'verify_tls' => 'boolean', 'connection_timeout' => 'integer'];
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class TenantEncryptionKey extends Model
{
use BelongsToTenant;
protected $fillable = ['tenant_id', 'version', 'wrapped_key', 'is_active', 'source', 'created_by', 'retired_at'];
protected $hidden = ['wrapped_key'];
protected static function booted(): void
{
static::creating(fn (self $key) => $key->uuid ??= (string) Str::uuid());
}
protected function casts(): array
{
return ['is_active' => 'boolean', 'retired_at' => 'immutable_datetime'];
}
}
+62
View File
@@ -0,0 +1,62 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Laravel\Fortify\Contracts\PasskeyUser;
use Laravel\Fortify\PasskeyAuthenticatable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
/**
* @property int $id
* @property string $name
* @property string $email
* @property Carbon|null $email_verified_at
* @property string $password
* @property string|null $two_factor_secret
* @property string|null $two_factor_recovery_codes
* @property Carbon|null $two_factor_confirmed_at
* @property string|null $remember_token
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*/
#[Fillable(['tenant_id', 'name', 'email', 'password', 'is_active'])]
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
class User extends Authenticatable implements PasskeyUser
{
/** @use HasFactory<UserFactory> */
use BelongsToTenant, HasApiTokens, HasFactory, HasRoles, Notifiable, PasskeyAuthenticatable, TwoFactorAuthenticatable;
protected static function booted(): void
{
static::creating(function (User $user): void {
$user->uuid ??= (string) Str::uuid();
});
}
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'two_factor_confirmed_at' => 'datetime',
'is_platform_admin' => 'boolean',
'is_active' => 'boolean',
];
}
}