210 lines
7.6 KiB
PHP
210 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use App\Models\Backup;
|
|
use App\Models\Customer;
|
|
use App\Models\Datacenter;
|
|
use App\Models\Host;
|
|
use App\Models\Instance;
|
|
use App\Models\InstanceMetric;
|
|
use App\Models\InstanceTraffic;
|
|
use App\Models\Order;
|
|
use App\Models\Seat;
|
|
use App\Models\Subscription;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Seeder;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* One complete customer, written as real rows.
|
|
*
|
|
* The panel used to be judged against fixtures baked into the views, and every
|
|
* round of that hid a different gap: a card with no data source, a figure that
|
|
* came from a translation string, a chart drawn from an array in a Blade file.
|
|
* A real customer with real rows makes the panel tell the truth about itself —
|
|
* whatever is missing shows up as missing.
|
|
*
|
|
* Everything hangs off one User, so removing the demo is one deletion:
|
|
*
|
|
* php artisan db:seed --class=DemoCustomerSeeder --force
|
|
* php artisan tinker --execute="App\Models\User::where('email','demo@clupilot.com')->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,
|
|
],
|
|
);
|
|
}
|
|
}
|
|
}
|