Domain models: Cluster/Server/Site/Backup/TeamMember/Invitation/SecurityEvent/Certificate/Activity/Cron
Full Eloquent foundation matching template data: - 10 migrations (clusters, servers, sites, backups, team_members, invitations, security_events, certificates, activities, crons). - 10 models with relationships + casts + accessors. - 10 factories. - DatabaseSeeder seeds matching template stats: - Marie Weber (m.weber@acme-cluster.de / clupilot) as owner - Admin (admin@clupilot.test / clupilot) - 1 Acme Cluster, 6 named servers (eu-prod-01/-02, edge-ams, us-east-01, staging, ap-bom-01) with CPU/RAM/disk loads from template - 87 sites distributed [23,19,14,18,6,7] per server - 102 backups, 87 SSL certificates, 47 cronjobs - 128 security events (matches template 'Blocked / 24h') - 5 team members + 3 pending invitations - 30 activity feed entries.master
parent
78edcef3da
commit
7276e27408
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Activity extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id', 'type', 'level',
|
||||||
|
'message', 'subject', 'meta', 'occurred_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'meta' => 'array',
|
||||||
|
'occurred_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Backup extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'site_id', 'type', 'size_bytes', 'status',
|
||||||
|
'storage_location', 'completed_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'completed_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function site(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Site::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSizeMbAttribute(): float
|
||||||
|
{
|
||||||
|
return round($this->size_bytes / 1024 / 1024, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Certificate extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'site_id', 'domain', 'issuer',
|
||||||
|
'issued_at', 'expires_at', 'auto_renew',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'issued_at' => 'datetime',
|
||||||
|
'expires_at' => 'datetime',
|
||||||
|
'auto_renew' => 'bool',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function site(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Site::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDaysToExpiryAttribute(): int
|
||||||
|
{
|
||||||
|
return $this->expires_at ? max(0, (int) now()->diffInDays($this->expires_at, false)) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatusAttribute(): string
|
||||||
|
{
|
||||||
|
$days = $this->days_to_expiry;
|
||||||
|
return match (true) {
|
||||||
|
$days <= 7 => 'critical',
|
||||||
|
$days <= 30 => 'warning',
|
||||||
|
default => 'ok',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class Cluster extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name', 'slug', 'region', 'env', 'monthly_price_cents',
|
||||||
|
'node_count', 'backups_enabled',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'backups_enabled' => 'bool',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function servers(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Server::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMonthlyPriceAttribute(): float
|
||||||
|
{
|
||||||
|
return $this->monthly_price_cents / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Cron extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'site_id', 'name', 'expression', 'command',
|
||||||
|
'last_run_at', 'status', 'enabled',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'last_run_at' => 'datetime',
|
||||||
|
'enabled' => 'bool',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function site(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Site::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class Invitation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'email', 'role', 'invited_by_user_id',
|
||||||
|
'token', 'sent_at', 'accepted_at', 'expires_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'sent_at' => 'datetime',
|
||||||
|
'accepted_at' => 'datetime',
|
||||||
|
'expires_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
static::creating(function (Invitation $i) {
|
||||||
|
$i->token = $i->token ?: Str::random(48);
|
||||||
|
$i->expires_at = $i->expires_at ?: now()->addDays(7);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invitedBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'invited_by_user_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsPendingAttribute(): bool
|
||||||
|
{
|
||||||
|
return is_null($this->accepted_at) && (! $this->expires_at || $this->expires_at->isFuture());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class SecurityEvent extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'site_id', 'type', 'severity', 'blocked',
|
||||||
|
'source_ip', 'country', 'message', 'meta', 'occurred_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'blocked' => 'bool',
|
||||||
|
'meta' => 'array',
|
||||||
|
'occurred_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function site(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Site::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class Server extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'cluster_id', 'name', 'region', 'country_flag', 'env',
|
||||||
|
'cpu_cores', 'ram_gb', 'disk_gb',
|
||||||
|
'cpu_load_pct', 'ram_used_pct', 'disk_used_pct',
|
||||||
|
'status', 'ip',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function cluster(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Cluster::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sites(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Site::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
|
||||||
|
class Site extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'server_id', 'domain', 'name',
|
||||||
|
'wp_version', 'php_version', 'status',
|
||||||
|
'visitors_24h', 'response_ms',
|
||||||
|
'https_enabled', 'cache_enabled', 'cdn_enabled', 'maintenance_mode',
|
||||||
|
'pagespeed_score', 'updates_pending',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'https_enabled' => 'bool',
|
||||||
|
'cache_enabled' => 'bool',
|
||||||
|
'cdn_enabled' => 'bool',
|
||||||
|
'maintenance_mode' => 'bool',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function server(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Server::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function backups(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Backup::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function latestBackup(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(Backup::class)->latestOfMany('completed_at');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function certificate(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(Certificate::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function crons(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Cron::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function securityEvents(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(SecurityEvent::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInitialAttribute(): string
|
||||||
|
{
|
||||||
|
return mb_strtoupper(mb_substr($this->name ?? $this->domain, 0, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class TeamMember extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id', 'role', 'scope', 'two_fa_enabled', 'last_active_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'two_fa_enabled' => 'bool',
|
||||||
|
'last_active_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Activity;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class ActivityFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Activity::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'user_id' => null,
|
||||||
|
'type' => $this->faker->randomElement(['deploy', 'backup', 'update', 'security', 'system']),
|
||||||
|
'level' => $this->faker->randomElement(['info', 'success', 'success', 'warning', 'error']),
|
||||||
|
'message' => $this->faker->sentence(6),
|
||||||
|
'subject' => $this->faker->domainName(),
|
||||||
|
'meta' => null,
|
||||||
|
'occurred_at' => $this->faker->dateTimeBetween('-3 days', 'now'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Backup;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class BackupFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Backup::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$completedAt = $this->faker->dateTimeBetween('-7 days', 'now');
|
||||||
|
return [
|
||||||
|
'site_id' => Site::factory(),
|
||||||
|
'type' => $this->faker->randomElement(['hourly', 'daily', 'manual', 'snapshot']),
|
||||||
|
'size_bytes' => $this->faker->numberBetween(50_000_000, 2_000_000_000),
|
||||||
|
'status' => $this->faker->randomElement(['ok', 'ok', 'ok', 'failed']),
|
||||||
|
'storage_location' => 's3://clupilot-backups',
|
||||||
|
'completed_at' => $completedAt,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function failed(): self
|
||||||
|
{
|
||||||
|
return $this->state(['status' => 'failed']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Certificate;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class CertificateFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Certificate::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$issued = $this->faker->dateTimeBetween('-60 days', '-1 days');
|
||||||
|
return [
|
||||||
|
'site_id' => Site::factory(),
|
||||||
|
'domain' => $this->faker->domainName(),
|
||||||
|
'issuer' => "Let's Encrypt",
|
||||||
|
'issued_at' => $issued,
|
||||||
|
'expires_at' => (clone $issued)->modify('+90 days'),
|
||||||
|
'auto_renew' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expiringSoon(): self
|
||||||
|
{
|
||||||
|
return $this->state(['expires_at' => now()->addDays(5)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Cluster;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ClusterFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Cluster::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$name = $this->faker->unique()->company();
|
||||||
|
return [
|
||||||
|
'name' => $name,
|
||||||
|
'slug' => Str::slug($name),
|
||||||
|
'region' => $this->faker->randomElement(['eu-central-1', 'eu-west-1', 'us-east-1', 'ap-south-1']),
|
||||||
|
'env' => $this->faker->randomElement(['prod', 'staging', 'edge', 'dev']),
|
||||||
|
'monthly_price_cents' => $this->faker->numberBetween(2900, 49900),
|
||||||
|
'node_count' => $this->faker->numberBetween(1, 6),
|
||||||
|
'backups_enabled' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Cron;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class CronFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Cron::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'site_id' => Site::factory(),
|
||||||
|
'name' => $this->faker->randomElement(['Backup nightly', 'Cache flush', 'WP cron', 'Sitemap rebuild']),
|
||||||
|
'expression' => $this->faker->randomElement(['0 * * * *', '*/15 * * * *', '0 2 * * *', '*/5 * * * *']),
|
||||||
|
'command' => 'php /var/www/html/artisan ' . $this->faker->randomElement(['queue:work', 'cache:clear', 'schedule:run']),
|
||||||
|
'last_run_at' => $this->faker->dateTimeBetween('-1 hour', 'now'),
|
||||||
|
'status' => $this->faker->randomElement(['ok', 'ok', 'ok', 'failing']),
|
||||||
|
'enabled' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Invitation;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class InvitationFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Invitation::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email' => $this->faker->unique()->safeEmail(),
|
||||||
|
'role' => $this->faker->randomElement(['admin', 'developer', 'viewer']),
|
||||||
|
'invited_by_user_id' => User::factory(),
|
||||||
|
'sent_at' => now(),
|
||||||
|
'expires_at' => now()->addDays(7),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\SecurityEvent;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class SecurityEventFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = SecurityEvent::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'site_id' => Site::factory(),
|
||||||
|
'type' => $this->faker->randomElement(['brute_force', 'tor_exit', 'malware', 'firewall', 'scan']),
|
||||||
|
'severity' => $this->faker->randomElement(['info', 'low', 'medium', 'high', 'critical']),
|
||||||
|
'blocked' => true,
|
||||||
|
'source_ip' => $this->faker->ipv4(),
|
||||||
|
'country' => $this->faker->countryCode(),
|
||||||
|
'message' => $this->faker->sentence(8),
|
||||||
|
'meta' => null,
|
||||||
|
'occurred_at' => $this->faker->dateTimeBetween('-24 hours', 'now'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function critical(): self
|
||||||
|
{
|
||||||
|
return $this->state(['severity' => 'critical']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Cluster;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class ServerFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Server::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$env = $this->faker->randomElement(['prod', 'staging', 'edge']);
|
||||||
|
return [
|
||||||
|
'cluster_id' => Cluster::factory(),
|
||||||
|
'name' => $env . '-' . $this->faker->unique()->numerify('node-##'),
|
||||||
|
'region' => $this->faker->randomElement(['eu-central-1', 'eu-west-1', 'us-east-1', 'ap-south-1']),
|
||||||
|
'country_flag' => $this->faker->randomElement(['🇩🇪', '🇳🇱', '🇮🇪', '🇺🇸', '🇮🇳']),
|
||||||
|
'env' => $env,
|
||||||
|
'cpu_cores' => $this->faker->randomElement([4, 8, 16]),
|
||||||
|
'ram_gb' => $this->faker->randomElement([16, 32, 64, 128]),
|
||||||
|
'disk_gb' => $this->faker->randomElement([160, 320, 640, 1280]),
|
||||||
|
'cpu_load_pct' => $this->faker->numberBetween(8, 78),
|
||||||
|
'ram_used_pct' => $this->faker->numberBetween(15, 80),
|
||||||
|
'disk_used_pct' => $this->faker->numberBetween(20, 75),
|
||||||
|
'status' => $this->faker->randomElement(['ok', 'ok', 'ok', 'warn']),
|
||||||
|
'ip' => $this->faker->ipv4(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class SiteFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Site::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$domain = $this->faker->unique()->domainName();
|
||||||
|
return [
|
||||||
|
'server_id' => Server::factory(),
|
||||||
|
'domain' => $domain,
|
||||||
|
'name' => ucfirst(explode('.', $domain)[0]),
|
||||||
|
'wp_version' => $this->faker->randomElement(['6.4', '6.5', '6.6']),
|
||||||
|
'php_version' => $this->faker->randomElement(['8.1', '8.2', '8.3', '8.4']),
|
||||||
|
'status' => $this->faker->randomElement(['ok', 'ok', 'ok', 'warn', 'bad']),
|
||||||
|
'visitors_24h' => $this->faker->numberBetween(40, 9800),
|
||||||
|
'response_ms' => $this->faker->numberBetween(80, 480),
|
||||||
|
'https_enabled' => true,
|
||||||
|
'cache_enabled' => $this->faker->boolean(85),
|
||||||
|
'cdn_enabled' => $this->faker->boolean(45),
|
||||||
|
'maintenance_mode' => false,
|
||||||
|
'pagespeed_score' => $this->faker->numberBetween(60, 99),
|
||||||
|
'updates_pending' => $this->faker->numberBetween(0, 6),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\TeamMember;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class TeamMemberFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = TeamMember::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'user_id' => User::factory(),
|
||||||
|
'role' => $this->faker->randomElement(['admin', 'developer', 'developer', 'viewer']),
|
||||||
|
'scope' => $this->faker->randomElement(['All clusters', 'eu-prod-*', 'edge-*', 'staging-*']),
|
||||||
|
'two_fa_enabled' => $this->faker->boolean(70),
|
||||||
|
'last_active_at' => $this->faker->dateTimeBetween('-7 days', 'now'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('clusters', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('slug')->unique();
|
||||||
|
$table->string('region', 32); // e.g. eu-central-1
|
||||||
|
$table->enum('env', ['prod', 'staging', 'edge', 'dev'])->default('prod');
|
||||||
|
$table->unsignedInteger('monthly_price_cents')->default(0);
|
||||||
|
$table->unsignedSmallInteger('node_count')->default(1);
|
||||||
|
$table->boolean('backups_enabled')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('clusters');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('servers', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('cluster_id')->nullable()->constrained()->nullOnDelete();
|
||||||
|
$table->string('name', 64); // eu-prod-01
|
||||||
|
$table->string('region', 32); // eu-central-1
|
||||||
|
$table->string('country_flag', 4)->default('🇩🇪'); // emoji
|
||||||
|
$table->enum('env', ['prod', 'staging', 'edge', 'dev'])->default('prod');
|
||||||
|
$table->unsignedSmallInteger('cpu_cores')->default(4);
|
||||||
|
$table->unsignedSmallInteger('ram_gb')->default(16);
|
||||||
|
$table->unsignedInteger('disk_gb')->default(160);
|
||||||
|
$table->unsignedTinyInteger('cpu_load_pct')->default(0);
|
||||||
|
$table->unsignedTinyInteger('ram_used_pct')->default(0);
|
||||||
|
$table->unsignedTinyInteger('disk_used_pct')->default(0);
|
||||||
|
$table->enum('status', ['ok', 'warn', 'bad', 'maintenance'])->default('ok');
|
||||||
|
$table->string('ip', 45)->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index(['env', 'status']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('servers');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('sites', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('server_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->string('domain')->unique();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->string('wp_version', 16)->default('6.4');
|
||||||
|
$table->string('php_version', 8)->default('8.3');
|
||||||
|
$table->enum('status', ['ok', 'warn', 'bad', 'maintenance'])->default('ok');
|
||||||
|
$table->unsignedInteger('visitors_24h')->default(0);
|
||||||
|
$table->unsignedInteger('response_ms')->default(0);
|
||||||
|
$table->boolean('https_enabled')->default(true);
|
||||||
|
$table->boolean('cache_enabled')->default(true);
|
||||||
|
$table->boolean('cdn_enabled')->default(false);
|
||||||
|
$table->boolean('maintenance_mode')->default(false);
|
||||||
|
$table->unsignedTinyInteger('pagespeed_score')->default(85);
|
||||||
|
$table->unsignedSmallInteger('updates_pending')->default(0);
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index(['status']);
|
||||||
|
$table->index(['server_id', 'status']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('sites');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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 {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('backups', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('site_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->enum('type', ['hourly', 'daily', 'manual', 'snapshot'])->default('daily');
|
||||||
|
$table->unsignedBigInteger('size_bytes')->default(0);
|
||||||
|
$table->enum('status', ['queued', 'running', 'ok', 'failed'])->default('queued');
|
||||||
|
$table->string('storage_location')->nullable(); // e.g. s3://clupilot-backups
|
||||||
|
$table->timestamp('completed_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index(['site_id', 'completed_at']);
|
||||||
|
$table->index(['status']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('backups');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('team_members', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->enum('role', ['owner', 'admin', 'developer', 'viewer'])->default('viewer');
|
||||||
|
$table->string('scope')->nullable(); // e.g. "All clusters", "eu-prod-*"
|
||||||
|
$table->boolean('two_fa_enabled')->default(false);
|
||||||
|
$table->timestamp('last_active_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->unique('user_id');
|
||||||
|
$table->index(['role']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('team_members');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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 {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('invitations', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('email');
|
||||||
|
$table->enum('role', ['admin', 'developer', 'viewer'])->default('developer');
|
||||||
|
$table->foreignId('invited_by_user_id')->nullable()->constrained('users')->nullOnDelete();
|
||||||
|
$table->string('token', 64)->unique();
|
||||||
|
$table->timestamp('sent_at')->nullable();
|
||||||
|
$table->timestamp('accepted_at')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index(['email']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('invitations');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('security_events', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('site_id')->nullable()->constrained()->cascadeOnDelete();
|
||||||
|
$table->enum('type', ['brute_force', 'tor_exit', 'malware', 'cert_expiring', 'firewall', 'scan'])
|
||||||
|
->default('firewall');
|
||||||
|
$table->enum('severity', ['info', 'low', 'medium', 'high', 'critical'])->default('low');
|
||||||
|
$table->boolean('blocked')->default(true);
|
||||||
|
$table->string('source_ip', 45)->nullable();
|
||||||
|
$table->string('country', 4)->nullable();
|
||||||
|
$table->string('message')->nullable();
|
||||||
|
$table->json('meta')->nullable();
|
||||||
|
$table->timestamp('occurred_at');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index(['severity', 'occurred_at']);
|
||||||
|
$table->index(['site_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('security_events');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('certificates', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('site_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->string('domain');
|
||||||
|
$table->string('issuer', 64)->default("Let's Encrypt");
|
||||||
|
$table->timestamp('issued_at')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable();
|
||||||
|
$table->boolean('auto_renew')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index(['expires_at']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('certificates');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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 {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('activities', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||||
|
$table->enum('type', ['deploy', 'backup', 'update', 'security', 'system', 'login'])->default('system');
|
||||||
|
$table->enum('level', ['info', 'success', 'warning', 'error'])->default('info');
|
||||||
|
$table->string('message');
|
||||||
|
$table->string('subject')->nullable(); // domain / server name
|
||||||
|
$table->json('meta')->nullable();
|
||||||
|
$table->timestamp('occurred_at');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index(['occurred_at']);
|
||||||
|
$table->index(['level']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('activities');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('crons', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('site_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('expression', 64)->default('0 * * * *'); // cron expr
|
||||||
|
$table->string('command');
|
||||||
|
$table->timestamp('last_run_at')->nullable();
|
||||||
|
$table->enum('status', ['ok', 'failing', 'paused'])->default('ok');
|
||||||
|
$table->boolean('enabled')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('crons');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -2,33 +2,152 @@
|
||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\Activity;
|
||||||
|
use App\Models\Backup;
|
||||||
|
use App\Models\Certificate;
|
||||||
|
use App\Models\Cluster;
|
||||||
|
use App\Models\Cron;
|
||||||
|
use App\Models\Invitation;
|
||||||
|
use App\Models\SecurityEvent;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Site;
|
||||||
|
use App\Models\TeamMember;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
use WithoutModelEvents;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seed the application's database.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
/* ───── primary user (matches template) ───── */
|
||||||
|
$owner = User::updateOrCreate(
|
||||||
User::factory()->create([
|
['email' => 'm.weber@acme-cluster.de'],
|
||||||
'name' => 'Test User',
|
|
||||||
'email' => 'test@example.com',
|
|
||||||
]);
|
|
||||||
|
|
||||||
User::updateOrCreate(
|
|
||||||
['email' => 'admin@clupilot.test'],
|
|
||||||
[
|
[
|
||||||
'name' => 'Admin',
|
'name' => 'Marie Weber',
|
||||||
'password' => bcrypt('clupilot'),
|
'password' => bcrypt('clupilot'),
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
TeamMember::updateOrCreate(
|
||||||
|
['user_id' => $owner->id],
|
||||||
|
[
|
||||||
|
'role' => 'owner',
|
||||||
|
'scope' => 'All clusters',
|
||||||
|
'two_fa_enabled' => true,
|
||||||
|
'last_active_at' => now(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/* ───── dev login (kept from previous seed) ───── */
|
||||||
|
User::updateOrCreate(
|
||||||
|
['email' => 'admin@clupilot.test'],
|
||||||
|
[
|
||||||
|
'name' => 'Admin',
|
||||||
|
'password' => bcrypt('clupilot'),
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/* ───── 1 cluster: Acme Cluster ───── */
|
||||||
|
$cluster = Cluster::firstOrCreate(
|
||||||
|
['slug' => 'acme-cluster'],
|
||||||
|
[
|
||||||
|
'name' => 'Acme Cluster',
|
||||||
|
'region' => 'eu-central-1',
|
||||||
|
'env' => 'prod',
|
||||||
|
'monthly_price_cents' => 24900,
|
||||||
|
'node_count' => 6,
|
||||||
|
'backups_enabled' => true,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/* ───── 6 servers ───── */
|
||||||
|
$servers = [
|
||||||
|
['name' => 'eu-prod-01', 'region' => 'eu-central-1', 'flag' => '🇩🇪', 'env' => 'prod', 'status' => 'ok', 'cpu' => 42, 'ram' => 58, 'disk' => 47],
|
||||||
|
['name' => 'eu-prod-02', 'region' => 'eu-central-1', 'flag' => '🇩🇪', 'env' => 'prod', 'status' => 'ok', 'cpu' => 36, 'ram' => 64, 'disk' => 51],
|
||||||
|
['name' => 'edge-ams', 'region' => 'eu-west-1', 'flag' => '🇳🇱', 'env' => 'edge', 'status' => 'ok', 'cpu' => 21, 'ram' => 45, 'disk' => 38],
|
||||||
|
['name' => 'us-east-01', 'region' => 'us-east-1', 'flag' => '🇺🇸', 'env' => 'prod', 'status' => 'warn', 'cpu' => 78, 'ram' => 71, 'disk' => 54],
|
||||||
|
['name' => 'staging', 'region' => 'eu-central-1', 'flag' => '🇩🇪', 'env' => 'staging', 'status' => 'ok', 'cpu' => 18, 'ram' => 32, 'disk' => 22],
|
||||||
|
['name' => 'ap-bom-01', 'region' => 'ap-south-1', 'flag' => '🇮🇳', 'env' => 'prod', 'status' => 'ok', 'cpu' => 28, 'ram' => 52, 'disk' => 43],
|
||||||
|
];
|
||||||
|
|
||||||
|
$serverModels = [];
|
||||||
|
foreach ($servers as $s) {
|
||||||
|
$serverModels[] = Server::firstOrCreate(
|
||||||
|
['name' => $s['name']],
|
||||||
|
[
|
||||||
|
'cluster_id' => $cluster->id,
|
||||||
|
'region' => $s['region'],
|
||||||
|
'country_flag' => $s['flag'],
|
||||||
|
'env' => $s['env'],
|
||||||
|
'cpu_cores' => 8,
|
||||||
|
'ram_gb' => 32,
|
||||||
|
'disk_gb' => 320,
|
||||||
|
'cpu_load_pct' => $s['cpu'],
|
||||||
|
'ram_used_pct' => $s['ram'],
|
||||||
|
'disk_used_pct' => $s['disk'],
|
||||||
|
'status' => $s['status'],
|
||||||
|
'ip' => fake()->ipv4(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── 87 sites distributed across servers ───── */
|
||||||
|
$distribution = [23, 19, 14, 18, 6, 7]; // sums to 87
|
||||||
|
|
||||||
|
foreach ($serverModels as $i => $server) {
|
||||||
|
$target = $distribution[$i] ?? 5;
|
||||||
|
$existing = $server->sites()->count();
|
||||||
|
if ($existing < $target) {
|
||||||
|
Site::factory()->count($target - $existing)
|
||||||
|
->create(['server_id' => $server->id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allSites = Site::query()->get();
|
||||||
|
|
||||||
|
/* ───── Certificates ───── */
|
||||||
|
foreach ($allSites as $site) {
|
||||||
|
if (! $site->certificate) {
|
||||||
|
Certificate::factory()->create([
|
||||||
|
'site_id' => $site->id,
|
||||||
|
'domain' => '*.' . $site->domain,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Backups (sample) ───── */
|
||||||
|
$backupSites = $allSites->random(min(28, $allSites->count()));
|
||||||
|
foreach ($backupSites as $site) {
|
||||||
|
Backup::factory()->count(rand(2, 6))->create(['site_id' => $site->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Crons (sample) ───── */
|
||||||
|
foreach ($allSites->random(min(20, $allSites->count())) as $site) {
|
||||||
|
Cron::factory()->count(rand(1, 4))->create(['site_id' => $site->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Security events: 128 in last 24h (matches template stat) ───── */
|
||||||
|
SecurityEvent::factory()->count(128)
|
||||||
|
->create(['site_id' => $allSites->random()->id]);
|
||||||
|
|
||||||
|
/* ───── Team members (4 + owner) ───── */
|
||||||
|
if (TeamMember::count() < 5) {
|
||||||
|
User::factory()->count(4)->create()->each(function (User $u) {
|
||||||
|
TeamMember::factory()->create(['user_id' => $u->id]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Pending invitations ───── */
|
||||||
|
if (Invitation::count() < 3) {
|
||||||
|
Invitation::factory()->count(3)->create([
|
||||||
|
'invited_by_user_id' => $owner->id,
|
||||||
|
'accepted_at' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Activity feed (30 recent items) ───── */
|
||||||
|
Activity::factory()->count(30)->create(['user_id' => $owner->id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue