Skip to content

Commit 7236cb8

Browse files
committed
feat(server): add dedicated server creation flow
1 parent 18f0f7f commit 7236cb8

31 files changed

Lines changed: 1159 additions & 353 deletions

app/Livewire/GlobalSearch.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,12 @@ private function loadCreatableItems()
11341134

11351135
public function navigateToResource($type)
11361136
{
1137+
if ($type === 'server') {
1138+
$this->dispatch('closeSearchModal');
1139+
1140+
return redirectRoute($this, 'server.create');
1141+
}
1142+
11371143
// Find the item by type - check regular items first, then services
11381144
$item = collect($this->creatableItems)->firstWhere('type', $type);
11391145

app/Livewire/Server/Create.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,21 @@ class Create extends Component
1111
{
1212
public $private_keys = [];
1313

14+
public ?string $selectedType = null;
15+
16+
public ?string $selectedTokenUuid = null;
17+
1418
public bool $limit_reached = false;
1519

1620
public bool $has_hetzner_tokens = false;
1721

18-
public function mount()
22+
public function mount(?string $selectedType = null, ?string $selectedTokenUuid = null): void
1923
{
24+
$this->selectedType = in_array($selectedType, ['hetzner', 'vultr', 'digital-ocean', 'manual'], true)
25+
? $selectedType
26+
: null;
27+
$this->selectedTokenUuid = $this->selectedType && $this->selectedType !== 'manual' ? $selectedTokenUuid : null;
28+
2029
$this->private_keys = PrivateKey::ownedByCurrentTeamCached();
2130
if (! isCloud()) {
2231
$this->limit_reached = false;

app/Livewire/Server/CreatePage.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace App\Livewire\Server;
4+
5+
use App\Models\CloudProviderToken;
6+
use Illuminate\Contracts\View\View;
7+
use Livewire\Component;
8+
9+
class CreatePage extends Component
10+
{
11+
public ?string $type = null;
12+
13+
public ?string $token_uuid = null;
14+
15+
public string $title = 'New Server';
16+
17+
public ?string $tokenProvider = null;
18+
19+
public ?string $tokenProviderName = null;
20+
21+
public bool $hasProviderTokens = false;
22+
23+
public function mount(?string $type = null, ?string $token_uuid = null): void
24+
{
25+
$this->type = $type;
26+
$this->token_uuid = $token_uuid;
27+
$this->tokenProvider = match ($type) {
28+
'hetzner' => 'hetzner',
29+
'vultr' => 'vultr',
30+
'digital-ocean' => 'digitalocean',
31+
default => null,
32+
};
33+
$this->tokenProviderName = match ($type) {
34+
'hetzner' => 'Hetzner',
35+
'vultr' => 'Vultr',
36+
'digital-ocean' => 'DigitalOcean',
37+
default => null,
38+
};
39+
$this->hasProviderTokens = $this->tokenProvider
40+
? CloudProviderToken::ownedByCurrentTeam()->where('provider', $this->tokenProvider)->exists()
41+
: false;
42+
$this->title = match ($type) {
43+
'hetzner' => 'Hetzner',
44+
'vultr' => 'Vultr',
45+
'digital-ocean' => 'DigitalOcean',
46+
'manual' => 'Manual',
47+
default => 'New Server',
48+
};
49+
}
50+
51+
public function render(): View
52+
{
53+
return view('livewire.server.create-page');
54+
}
55+
}

app/Livewire/Server/New/ByDigitalOcean.php

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class ByDigitalOcean extends Component
3333

3434
public ?int $selected_token_id = null;
3535

36+
public ?string $selectedTokenUuid = null;
37+
3638
public array $regions = [];
3739

3840
public array $images = [];
@@ -74,18 +76,24 @@ class ByDigitalOcean extends Component
7476

7577
public bool $from_onboarding = false;
7678

77-
public function mount()
79+
public function mount(?string $selectedTokenUuid = null)
7880
{
7981
try {
8082
$this->authorize('viewAny', CloudProviderToken::class);
8183
$this->loadTokens();
84+
$this->selectTokenFromUrl($selectedTokenUuid);
8285
$this->loadSavedCloudInitScripts();
8386
$this->server_name = generate_random_name();
8487
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get();
8588

8689
if ($this->private_keys->count() > 0) {
8790
$this->private_key_id = $this->private_keys->first()->id;
8891
}
92+
93+
if ($this->selectedTokenUuid) {
94+
$this->current_step = 2;
95+
$this->loading_data = true;
96+
}
8997
} catch (\Throwable $e) {
9098
return handleError($e, $this);
9199
}
@@ -174,9 +182,27 @@ protected function messages(): array
174182
];
175183
}
176184

177-
public function selectToken(int $tokenId): void
185+
public function selectToken(int $tokenId): mixed
178186
{
179187
$this->selected_token_id = $tokenId;
188+
189+
return $this->nextStep();
190+
}
191+
192+
private function selectTokenFromUrl(?string $selectedTokenUuid): void
193+
{
194+
if (! $selectedTokenUuid) {
195+
return;
196+
}
197+
198+
$token = $this->available_tokens->firstWhere('uuid', $selectedTokenUuid);
199+
200+
if (! $token) {
201+
return;
202+
}
203+
204+
$this->selectedTokenUuid = $selectedTokenUuid;
205+
$this->selected_token_id = $token->id;
180206
}
181207

182208
private function getDigitalOceanToken(): string
@@ -197,26 +223,46 @@ public function nextStep()
197223
]);
198224

199225
try {
200-
$digitalOceanToken = $this->getDigitalOceanToken();
201-
202-
if (! $digitalOceanToken) {
203-
return $this->dispatch('error', 'Please select a valid DigitalOcean token.');
226+
if (! $this->selectedTokenUuid) {
227+
$token = $this->available_tokens->firstWhere('id', $this->selected_token_id);
228+
229+
if ($token) {
230+
return $this->redirectRoute('server.create.token', [
231+
'type' => 'digital-ocean',
232+
'token_uuid' => $token->uuid,
233+
], navigate: true);
234+
}
204235
}
205236

206-
$this->loadDigitalOceanData($digitalOceanToken);
207237
$this->current_step = 2;
238+
$this->loading_data = true;
208239
} catch (\Throwable $e) {
209240
return handleError($e, $this);
210241
}
211242
}
212243

213-
public function previousStep(): void
244+
public function previousStep(): mixed
214245
{
246+
if ($this->selectedTokenUuid) {
247+
return $this->redirectRoute('server.create.type', ['type' => 'digital-ocean'], navigate: true);
248+
}
249+
215250
$this->current_step = 1;
251+
252+
return null;
216253
}
217254

218-
private function loadDigitalOceanData(string $token): void
255+
public function loadDigitalOceanData(): void
219256
{
257+
$token = $this->getDigitalOceanToken();
258+
259+
if (! $token) {
260+
$this->loading_data = false;
261+
$this->dispatch('error', 'Please select a valid DigitalOcean token.');
262+
263+
return;
264+
}
265+
220266
$this->loading_data = true;
221267

222268
try {

app/Livewire/Server/New/ByHetzner.php

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class ByHetzner extends Component
3737
// Step 1: Token selection
3838
public ?int $selected_token_id = null;
3939

40+
public ?string $selectedTokenUuid = null;
41+
4042
// Step 2: Server configuration
4143
public array $locations = [];
4244

@@ -89,18 +91,24 @@ class ByHetzner extends Component
8991

9092
public bool $from_onboarding = false;
9193

92-
public function mount()
94+
public function mount(?string $selectedTokenUuid = null)
9395
{
9496
try {
9597
$this->authorize('viewAny', CloudProviderToken::class);
9698
$this->loadTokens();
99+
$this->selectTokenFromUrl($selectedTokenUuid);
97100
$this->loadSavedCloudInitScripts();
98101
$this->server_name = generate_random_name();
99102
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get();
100103

101104
if ($this->private_keys->count() > 0) {
102105
$this->private_key_id = $this->private_keys->first()->id;
103106
}
107+
108+
if ($this->selectedTokenUuid) {
109+
$this->current_step = 2;
110+
$this->loading_data = true;
111+
}
104112
} catch (\Throwable $e) {
105113
return handleError($e, $this);
106114
}
@@ -207,9 +215,27 @@ protected function messages(): array
207215
];
208216
}
209217

210-
public function selectToken(int $tokenId)
218+
public function selectToken(int $tokenId): mixed
211219
{
212220
$this->selected_token_id = $tokenId;
221+
222+
return $this->nextStep();
223+
}
224+
225+
private function selectTokenFromUrl(?string $selectedTokenUuid): void
226+
{
227+
if (! $selectedTokenUuid) {
228+
return;
229+
}
230+
231+
$token = $this->available_tokens->firstWhere('uuid', $selectedTokenUuid);
232+
233+
if (! $token) {
234+
return;
235+
}
236+
237+
$this->selectedTokenUuid = $selectedTokenUuid;
238+
$this->selected_token_id = $token->id;
213239
}
214240

215241
private function validateHetznerToken(string $token): bool
@@ -244,29 +270,45 @@ public function nextStep()
244270
]);
245271

246272
try {
247-
$hetznerToken = $this->getHetznerToken();
248-
249-
if (! $hetznerToken) {
250-
return $this->dispatch('error', 'Please select a valid Hetzner token.');
273+
if (! $this->selectedTokenUuid) {
274+
$token = $this->available_tokens->firstWhere('id', $this->selected_token_id);
275+
276+
if ($token) {
277+
return $this->redirectRoute('server.create.token', [
278+
'type' => 'hetzner',
279+
'token_uuid' => $token->uuid,
280+
], navigate: true);
281+
}
251282
}
252283

253-
// Load Hetzner data
254-
$this->loadHetznerData($hetznerToken);
255-
256-
// Move to step 2
284+
// Move to step 2; provider data is loaded after initial render via wire:init.
257285
$this->current_step = 2;
286+
$this->loading_data = true;
258287
} catch (\Throwable $e) {
259288
return handleError($e, $this);
260289
}
261290
}
262291

263292
public function previousStep()
264293
{
294+
if ($this->selectedTokenUuid) {
295+
return $this->redirectRoute('server.create.type', ['type' => 'hetzner'], navigate: true);
296+
}
297+
265298
$this->current_step = 1;
266299
}
267300

268-
private function loadHetznerData(string $token)
301+
public function loadHetznerData(): void
269302
{
303+
$token = $this->getHetznerToken();
304+
305+
if (! $token) {
306+
$this->loading_data = false;
307+
$this->dispatch('error', 'Please select a valid Hetzner token.');
308+
309+
return;
310+
}
311+
270312
$this->loading_data = true;
271313
$this->selectedHetznerSshKeyIds = [];
272314
$this->selectedHetznerFirewallIds = [];

0 commit comments

Comments
 (0)