first()?->delete()" * * Safe to run twice: every row is matched on a natural key first. */ class DemoCustomerSeeder extends Seeder { private const EMAIL = 'demo@clupilot.com'; public function run(): void { // The demo account is a real login on a panel that is reachable from the // public internet, so it never gets a password written in a source file. // DEMO_PASSWORD if the operator set one, otherwise a fresh random one // printed once — a weak default would sit on production forever, and // "it is only the demo" is how the first account gets taken. $password = (string) (env('DEMO_PASSWORD') ?: Str::password(24)); $user = User::firstOrCreate( ['email' => self::EMAIL], ['name' => 'Kanzlei Muster', 'password' => Hash::make($password)], ); if ($user->wasRecentlyCreated) { $this->command?->warn('Demo password (shown once): '.$password); } $customer = Customer::firstOrCreate( ['user_id' => $user->id], ['name' => 'Kanzlei Muster', 'email' => self::EMAIL, 'contact_name' => 'Dr. M. Muster', 'status' => 'active'], ); $datacenter = Datacenter::firstOrCreate( ['code' => 'fsn'], ['name' => 'Falkenstein', 'location' => 'DE', 'active' => true], ); $host = Host::query()->where('datacenter', $datacenter->code)->first() ?? Host::factory()->create(['datacenter' => $datacenter->code, 'name' => 'pve-fsn-01', 'status' => 'active']); $order = Order::firstOrCreate( ['customer_id' => $customer->id, 'type' => 'plan'], [ 'plan' => 'team', 'amount_cents' => 17900, 'currency' => 'EUR', 'datacenter' => $datacenter->code, 'status' => 'paid', ], ); $instance = Instance::firstOrCreate( ['customer_id' => $customer->id], [ 'order_id' => $order->id, 'host_id' => $host->id, 'vmid' => 9001, 'plan' => 'team', 'quota_gb' => 500, 'disk_gb' => 500, 'ram_mb' => 8192, 'cores' => 4, 'traffic_addons' => 0, 'guest_ip' => '10.20.0.31', 'subdomain' => 'kanzlei-muster', 'status' => 'active', 'cert_ok' => true, 'route_written' => true, ], ); Subscription::firstOrCreate( ['customer_id' => $customer->id], [ 'order_id' => $order->id, 'instance_id' => $instance->id, 'plan' => 'team', 'tier' => 2, 'term' => 'monthly', 'price_cents' => 17900, 'currency' => 'EUR', 'quota_gb' => 500, 'traffic_gb' => 3072, 'seats' => 25, 'ram_mb' => 8192, 'cores' => 4, 'disk_gb' => 500, 'performance' => 'enhanced', 'status' => 'active', 'started_at' => now()->subMonths(4), 'current_period_start' => now()->startOfMonth(), 'current_period_end' => now()->addMonth()->startOfMonth(), ], ); $this->seats($customer); $this->backups($instance); $this->traffic($instance); $this->metrics($instance); $this->command?->info('Demo customer ready: '.self::EMAIL); } private function seats(Customer $customer): void { $people = [ ['Dr. M. Muster', 'demo@clupilot.com', 'owner', 'active'], ['S. Berger', 'berger@kanzlei-muster.test', 'admin', 'active'], ['A. Wimmer', 'wimmer@kanzlei-muster.test', 'member', 'active'], ['T. Klein', 'klein@kanzlei-muster.test', 'member', 'active'], ['P. Hofer', 'hofer@kanzlei-muster.test', 'member', 'invited'], ['Steuerberatung Ost', 'extern@partner.test', 'readonly', 'active'], ]; foreach ($people as [$name, $email, $role, $status]) { Seat::firstOrCreate( ['customer_id' => $customer->id, 'email' => $email], ['name' => $name, 'role' => $role, 'status' => $status, 'invited_at' => now()->subDays(30)], ); } } private function backups(Instance $instance): void { Backup::firstOrCreate( ['instance_id' => $instance->id, 'schedule' => 'täglich 03:00'], ['external_job_id' => 'demo-backup', 'status' => 'ok', 'last_ok_at' => now()->setTime(3, 12)], ); } private function traffic(Instance $instance): void { InstanceTraffic::firstOrCreate( ['instance_id' => $instance->id, 'period' => InstanceTraffic::currentPeriod()], [ 'rx_bytes' => (int) (340 * 1024 ** 3), 'tx_bytes' => (int) (72 * 1024 ** 3), 'last_netin' => 0, 'last_netout' => 0, 'sampled_at' => now(), ], ); } /** * Thirty days of samples: a disk that fills slowly, transfer that varies, * and one day where two checks out of 288 failed. * * Deliberately not a smooth curve — a straight line is the first thing that * gives a demo away, and a chart that never wobbles teaches nobody what a * real one looks like. */ private function metrics(Instance $instance): void { $total = (int) (500 * 1024 ** 3); $used = (int) (198 * 1024 ** 3); $wobble = [1.0, 0.6, 1.4, 0.9, 1.7, 0.4, 0.2, 1.1, 1.5, 0.8]; foreach (range(29, 0) as $offset) { $day = Carbon::today()->subDays($offset); $i = 29 - $offset; $used += (int) ($wobble[$i % 10] * 0.42 * 1024 ** 3); InstanceMetric::updateOrCreate( ['instance_id' => $instance->id, 'day' => $day->toDateString()], [ 'disk_used_bytes' => $used, 'disk_total_bytes' => $total, 'rx_bytes' => (int) ((9 + $wobble[$i % 10] * 4) * 1024 ** 3), 'tx_bytes' => (int) ((2 + $wobble[($i + 3) % 10]) * 1024 ** 3), // 288 checks a day is one every five minutes. 'checks_total' => 288, 'checks_ok' => $offset === 12 ? 286 : 288, ], ); } } }