feat: initialize Infra Manager with React, Tailwind CSS, and Inertia.js

- Added main application entry point (app.jsx) and bootstrap file for React setup.
- Created Wiki index page (Index.jsx) for documentation and file management.
- Implemented utility functions for formatting and status handling in utils.js.
- Set up Blade template (app.blade.php) for Inertia.js integration.
- Defined API routes for metrics and terminal access in api.php.
- Established console commands for scheduled tasks in console.php.
- Created web routes for various application features including authentication, hypervisors, containers, cloud accounts, security, backups, and wiki management in web.php.
- Developed setup scripts for Windows (setup.ps1) and Linux (setup.sh) environments.
- Configured Tailwind CSS for styling with custom themes and animations in tailwind.config.js.
- Set up Vite for asset bundling and development server in vite.config.js.
This commit is contained in:
I-Fun
2026-04-07 20:29:32 +07:00
commit c649f055d8
126 changed files with 8122 additions and 0 deletions

22
app/Models/Alert.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Alert extends Model
{
protected $fillable = [
'rule_id', 'target_type', 'target_id', 'target_name',
'title', 'message', 'severity', 'status',
'acknowledged_by', 'acknowledged_at', 'resolved_at',
];
protected $casts = [
'acknowledged_at' => 'datetime',
'resolved_at' => 'datetime',
];
public function rule() { return $this->belongsTo(AlertRule::class); }
public function acknowledgedBy() { return $this->belongsTo(User::class, 'acknowledged_by'); }
}

14
app/Models/AlertRule.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AlertRule extends Model
{
protected $fillable = [
'name', 'target_type', 'metric_name', 'condition', 'threshold',
'duration_seconds', 'severity', 'notification_channels', 'is_active', 'created_by',
];
protected $casts = ['notification_channels' => 'array', 'is_active' => 'boolean'];
}

16
app/Models/Backup.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Backup extends Model
{
protected $fillable = [
'policy_id', 'target_type', 'target_id', 'target_name',
'status', 'storage_path', 'size_bytes', 'checksum', 'metadata', 'notes',
'expires_at', 'created_by', 'started_at', 'completed_at',
];
protected $casts = ['metadata' => 'array', 'expires_at' => 'datetime', 'started_at' => 'datetime', 'completed_at' => 'datetime'];
public function policy() { return $this->belongsTo(BackupPolicy::class); }
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class BackupPolicy extends Model
{
protected $fillable = [
'name', 'target_type', 'target_ids', 'schedule', 'retention_type', 'retention_value',
'storage_type', 'storage_config', 'is_active', 'compression', 'encryption', 'created_by',
];
protected $casts = ['target_ids' => 'array', 'storage_config' => 'array', 'compression' => 'array', 'encryption' => 'array', 'is_active' => 'boolean'];
public function backups() { return $this->hasMany(Backup::class, 'policy_id'); }
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Crypt;
class Certificate extends Model
{
use SoftDeletes;
protected $fillable = [
'name', 'domain', 'san', 'certificate', 'private_key', 'chain',
'issuer', 'type', 'auto_renew', 'issued_at', 'expires_at', 'tags', 'status', 'created_by',
];
protected $hidden = ['certificate', 'private_key', 'chain'];
protected $casts = [
'san' => 'array',
'tags' => 'array',
'auto_renew' => 'boolean',
'issued_at' => 'datetime',
'expires_at' => 'datetime',
];
public function setPrivateKeyAttribute(string $value): void
{
$this->attributes['private_key'] = Crypt::encryptString($value);
}
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Crypt;
class CloudAccount extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name', 'provider', 'account_id', 'credentials', 'region',
'regions', 'is_active', 'status', 'tags', 'last_sync_at', 'created_by',
];
protected $hidden = ['credentials'];
protected $casts = [
'is_active' => 'boolean',
'regions' => 'array',
'tags' => 'array',
'last_sync_at' => 'datetime',
];
public function setCredentialsAttribute(array $value): void
{
$this->attributes['credentials'] = Crypt::encryptString(json_encode($value));
}
public function getCredentialsAttribute(string $value): array
{
return json_decode(Crypt::decryptString($value), true) ?? [];
}
public function resources() { return $this->hasMany(CloudResource::class, 'account_id'); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class CloudResource extends Model
{
protected $fillable = [
'account_id', 'resource_id', 'resource_type', 'name',
'status', 'region', 'properties', 'tags', 'monthly_cost',
];
protected $casts = ['properties' => 'array', 'tags' => 'array'];
public function account() { return $this->belongsTo(CloudAccount::class); }
}

23
app/Models/Container.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Container extends Model
{
protected $fillable = [
'endpoint_id', 'container_id', 'name', 'image', 'image_id',
'status', 'state', 'ports', 'volumes', 'env', 'labels', 'networks', 'created_at_unix',
];
protected $casts = [
'ports' => 'array',
'volumes' => 'array',
'env' => 'array',
'labels' => 'array',
'networks' => 'array',
];
public function endpoint() { return $this->belongsTo(DockerEndpoint::class); }
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class DockerEndpoint extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name', 'type', 'host', 'tls_enabled', 'tls_cert', 'tls_key', 'tls_ca',
'status', 'info', 'last_sync_at', 'created_by',
];
protected $hidden = ['tls_cert', 'tls_key', 'tls_ca'];
protected $casts = [
'tls_enabled' => 'boolean',
'info' => 'array',
'last_sync_at' => 'datetime',
];
public function containers() { return $this->hasMany(Container::class, 'endpoint_id'); }
public function stacks() { return $this->hasMany(Stack::class, 'endpoint_id'); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}

70
app/Models/Hypervisor.php Normal file
View File

@@ -0,0 +1,70 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Crypt;
class Hypervisor extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name', 'type', 'version', 'host', 'port',
'username', 'password', 'ssl_verify', 'datacenter',
'status', 'metadata', 'last_sync_at', 'last_error', 'created_by',
];
protected $casts = [
'ssl_verify' => 'boolean',
'metadata' => 'array',
'last_sync_at' => 'datetime',
];
protected $hidden = ['password'];
public function setPasswordAttribute(string $value): void
{
$this->attributes['password'] = Crypt::encryptString($value);
}
public function getPasswordAttribute(string $value): string
{
return Crypt::decryptString($value);
}
public function virtualMachines()
{
return $this->hasMany(VirtualMachine::class);
}
public function templates()
{
return $this->hasMany(VmTemplate::class);
}
public function createdBy()
{
return $this->belongsTo(User::class, 'created_by');
}
public function isOnline(): bool
{
return $this->status === 'online';
}
public function getTypeLabel(): string
{
return match($this->type) {
'vcenter' => 'VMware vCenter',
'esxi' => 'VMware ESXi',
'proxmox' => 'Proxmox VE',
'hyper-v' => 'Microsoft Hyper-V',
'xen' => 'Citrix XenServer',
'kvm' => 'KVM/QEMU',
default => ucfirst($this->type),
};
}
}

35
app/Models/LdapConfig.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
class LdapConfig extends Model
{
protected $fillable = [
'name', 'host', 'port', 'use_ssl', 'use_tls', 'base_dn',
'bind_dn', 'bind_password', 'user_search_base', 'user_filter',
'group_search_base', 'group_filter', 'admin_group_dn',
'attribute_map', 'is_active', 'status',
];
protected $hidden = ['bind_password'];
protected $casts = [
'use_ssl' => 'boolean',
'use_tls' => 'boolean',
'is_active' => 'boolean',
'attribute_map' => 'array',
];
public function setBindPasswordAttribute(?string $value): void
{
$this->attributes['bind_password'] = $value ? Crypt::encryptString($value) : null;
}
public function getBindPasswordAttribute(?string $value): ?string
{
return $value ? Crypt::decryptString($value) : null;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Crypt;
class PasswordEntry extends Model
{
use SoftDeletes;
protected $fillable = [
'user_id', 'title', 'category', 'url', 'username',
'password', 'notes', 'custom_fields', 'tags',
'is_shared', 'shared_with', 'expires_at', 'last_used_at',
];
protected $hidden = ['password', 'notes', 'custom_fields'];
protected $casts = [
'tags' => 'array',
'shared_with' => 'array',
'is_shared' => 'boolean',
'expires_at' => 'datetime',
'last_used_at' => 'datetime',
];
public function setPasswordAttribute(string $value): void
{
$this->attributes['password'] = Crypt::encryptString($value);
}
public function getDecryptedPassword(): string
{
return Crypt::decryptString($this->attributes['password']);
}
public function user() { return $this->belongsTo(User::class); }
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SecurityScan extends Model
{
protected $fillable = [
'target_type', 'target_id', 'target_name', 'scan_type', 'status',
'score', 'grade', 'critical_count', 'high_count', 'medium_count', 'low_count', 'info_count',
'findings', 'raw_output', 'initiated_by', 'started_at', 'completed_at',
];
protected $casts = [
'findings' => 'array',
'started_at' => 'datetime',
'completed_at' => 'datetime',
];
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SecurityScore extends Model
{
protected $fillable = ['target_type', 'target_id', 'score', 'grade', 'breakdown', 'calculated_at'];
protected $casts = ['breakdown' => 'array', 'calculated_at' => 'datetime'];
}

57
app/Models/Setting.php Normal file
View File

@@ -0,0 +1,57 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
class Setting extends Model
{
protected $fillable = ['group', 'key', 'value', 'type', 'is_encrypted'];
protected $casts = [
'is_encrypted' => 'boolean',
];
public function getValueAttribute($value)
{
if ($this->is_encrypted && $value) {
$value = Crypt::decryptString($value);
}
return match ($this->type) {
'boolean' => (bool) $value,
'integer' => (int) $value,
'json' => json_decode($value, true),
default => $value,
};
}
public function setValueAttribute($value): void
{
if ($this->type === 'json') {
$value = json_encode($value);
}
if ($this->is_encrypted) {
$value = Crypt::encryptString((string) $value);
}
$this->attributes['value'] = $value;
}
public static function get(string $key, $default = null)
{
$setting = static::where('key', $key)->first();
return $setting ? $setting->value : $default;
}
public static function set(string $key, $value, string $group = 'general', string $type = 'string'): void
{
static::updateOrCreate(['key' => $key], [
'group' => $group,
'value' => $value,
'type' => $type,
]);
}
}

15
app/Models/Stack.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Stack extends Model
{
use SoftDeletes;
protected $fillable = ['endpoint_id', 'name', 'type', 'compose_content', 'env_vars', 'status', 'remote_id', 'created_by'];
protected $casts = ['env_vars' => 'array'];
public function endpoint() { return $this->belongsTo(DockerEndpoint::class); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}

58
app/Models/User.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Activitylog\Traits\CausesActivity;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, HasRoles, Notifiable, SoftDeletes, CausesActivity;
protected $fillable = [
'name', 'email', 'password', 'avatar', 'auth_source',
'ldap_guid', 'ldap_domain', 'is_active', 'mfa_enabled',
'mfa_secret', 'mfa_recovery_codes', 'last_login_at',
'last_login_ip', 'preferences',
];
protected $hidden = [
'password', 'remember_token', 'mfa_secret', 'mfa_recovery_codes',
];
protected $casts = [
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
'is_active' => 'boolean',
'mfa_enabled' => 'boolean',
'mfa_recovery_codes' => 'encrypted:array',
'mfa_secret' => 'encrypted',
'preferences' => 'array',
'password' => 'hashed',
];
public function getAvatarUrlAttribute(): string
{
if ($this->avatar) {
return asset('storage/' . $this->avatar);
}
$hash = md5(strtolower(trim($this->email)));
return "https://www.gravatar.com/avatar/{$hash}?d=identicon&s=128";
}
public function isLdapUser(): bool
{
return $this->auth_source === 'ldap';
}
public function passwordEntries()
{
return $this->hasMany(PasswordEntry::class);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class VirtualMachine extends Model
{
protected $fillable = [
'hypervisor_id', 'remote_id', 'name', 'status', 'os_type', 'os_version',
'cpu_count', 'memory_mb', 'disk_gb', 'ip_address', 'mac_address',
'host_node', 'cluster', 'datastore', 'tags', 'metadata',
'cpu_usage', 'memory_usage', 'created_by',
];
protected $casts = [
'tags' => 'array',
'metadata' => 'array',
];
public function hypervisor() { return $this->belongsTo(Hypervisor::class); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}

19
app/Models/VmTemplate.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class VmTemplate extends Model
{
use SoftDeletes;
protected $fillable = [
'hypervisor_id', 'name', 'description', 'os_type', 'os_version',
'cpu_count', 'memory_mb', 'disk_gb', 'network_config', 'customization',
'variables', 'provision_script', 'status', 'remote_id', 'tags', 'created_by',
];
protected $casts = ['network_config' => 'array', 'customization' => 'array', 'variables' => 'array', 'tags' => 'array'];
public function hypervisor() { return $this->belongsTo(Hypervisor::class); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}

25
app/Models/WikiPage.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class WikiPage extends Model
{
use SoftDeletes;
protected $fillable = [
'space_id', 'parent_id', 'title', 'slug', 'content', 'content_html',
'tags', 'is_published', 'order', 'created_by', 'updated_by',
];
protected $casts = ['tags' => 'array', 'is_published' => 'boolean'];
public function space() { return $this->belongsTo(WikiSpace::class); }
public function parent() { return $this->belongsTo(WikiPage::class, 'parent_id'); }
public function children() { return $this->hasMany(WikiPage::class, 'parent_id'); }
public function revisions() { return $this->hasMany(WikiPageRevision::class, 'page_id'); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
public function updatedBy() { return $this->belongsTo(User::class, 'updated_by'); }
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WikiPageRevision extends Model
{
protected $fillable = ['page_id', 'content', 'change_summary', 'created_by'];
public function page() { return $this->belongsTo(WikiPage::class); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}

18
app/Models/WikiSpace.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class WikiSpace extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['name', 'slug', 'description', 'icon', 'color', 'is_public', 'created_by'];
protected $casts = ['is_public' => 'boolean'];
public function pages() { return $this->hasMany(WikiPage::class, 'space_id'); }
public function createdBy() { return $this->belongsTo(User::class, 'created_by'); }
}