Skip to content

Commit 16bc52c

Browse files
committed
feat: #122 rework swarm-related management interfaces
1 parent f493dd3 commit 16bc52c

18 files changed

+336
-365
lines changed

app/Http/Controllers/NodeController.php

-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use App\Models\Node;
99
use App\Models\NodeTaskGroupType;
1010
use App\Models\Service;
11-
use App\Models\Swarm;
1211
use Illuminate\Http\Request;
1312
use Illuminate\Support\Facades\DB;
1413
use Inertia\Inertia;
@@ -87,13 +86,11 @@ public function show(Node $node)
8786

8887
return Inertia::render('Nodes/Show', [
8988
'node' => $node,
90-
'swarms' => Swarm::all(),
9189
'isLastNode' => $node->team->nodes->count() === 1,
9290
'initTaskGroup' => $initTaskGroup ?: $joinTaskGroup,
9391
'lastAgentVersion' => $lastAgentVersion,
9492
'agentUpgradeTaskGroup' => $taskGroup?->is_completed ? null : $taskGroup,
9593
'registryUpdateTaskGroup' => $registryTaskGroup?->is_completed ? null : $registryTaskGroup,
96-
'swarmsQuotaReached' => auth()->user()->currentTeam->quotas()->swarms->quotaReached(),
9794
]);
9895
}
9996

app/Http/Controllers/ServiceController.php

+14-9
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,18 @@ public function index()
3333
*/
3434
public function create()
3535
{
36-
$swarms = Swarm::all();
36+
$swarm = auth()->user()->currentTeam->swarms()->first();
3737

38-
$networks = count($swarms) ? $swarms[0]->networks : [];
39-
$nodes = count($swarms) ? $swarms[0]->nodes : [];
40-
$dockerRegistries = count($swarms) ? $swarms[0]->data->registries : [];
41-
$s3Storages = count($swarms) ? $swarms[0]->data->s3Storages : [];
38+
$networks = $swarm->networks;
39+
$nodes = $swarm->nodes;
40+
$dockerRegistries = $swarm->data->registries;
41+
$s3Storages = $swarm->data->s3Storages;
4242

4343
$deploymentData = DeploymentData::make([
44-
'networkName' => count($networks) ? $networks[0]->name : null,
44+
'networkName' => $networks->first()->name,
4545
]);
4646

4747
return Inertia::render('Services/Create', [
48-
'swarms' => $swarms,
4948
'networks' => $networks,
5049
'nodes' => $nodes,
5150
'deploymentData' => $deploymentData,
@@ -62,15 +61,21 @@ public function store(StoreServiceRequest $request)
6261
{
6362
$deploymentData = DeploymentData::validateAndCreate($request->get('deploymentData'));
6463

64+
$team = auth()->user()->currentTeam;
65+
$swarm = $team->swarms()->firstOrFail();
66+
6567
$service = Service::make($request->validated());
66-
$service->team_id = auth()->user()->current_team_id;
68+
$service->team_id = $team->id;
69+
$service->swarm_id = $swarm->id;
70+
6771
DB::transaction(function () use ($service, $deploymentData) {
6872
$service->save();
6973

7074
$service->deploy($deploymentData);
7175
});
7276

73-
return to_route('services.deployments', ['service' => $service->id]);
77+
return to_route('services.deployments', $service)
78+
->with('success', 'Service created and deployment scheduled successfully.');
7479
}
7580

7681
/**

app/Http/Controllers/SwarmTaskController.php

+12-6
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,22 @@ public function initCluster(InitClusterFormRequest $request)
217217
public function joinCluster(JoinClusterFormRequest $request)
218218
{
219219
DB::transaction(function () use ($request) {
220+
$node = Node::findOrFail($request->node_id);
221+
$swarm = $node->team->swarms()->first();
222+
223+
if (! $swarm) {
224+
throw new \Exception('No swarm found for the node\'s team.');
225+
}
226+
220227
$taskGroup = NodeTaskGroup::create([
221228
'type' => NodeTaskGroupType::JoinSwarm,
222-
'swarm_id' => $request->swarm_id,
223-
'team_id' => auth()->user()->currentTeam->id,
224-
'node_id' => $request->node_id,
229+
'swarm_id' => $swarm->id,
230+
'team_id' => $node->team_id,
231+
'node_id' => $node->id,
225232
'invoker_id' => auth()->user()->id,
226233
]);
227234

228-
$node = Node::findOrFail($request->node_id);
229-
$node->swarm_id = $request->swarm_id;
235+
$node->swarm_id = $swarm->id;
230236
$node->save();
231237

232238
$remoteAddrs = collect($taskGroup->swarm->data->managerNodes)->map(fn (SwarmData\ManagerNode $node) => $node->addr)->toArray();
@@ -238,7 +244,7 @@ public function joinCluster(JoinClusterFormRequest $request)
238244

239245
$taskGroup->tasks()->create([
240246
'type' => NodeTaskType::JoinSwarm,
241-
'meta' => JoinSwarmMeta::from(['swarmId' => $request->swarm_id, 'role' => $request->role]),
247+
'meta' => JoinSwarmMeta::from(['swarmId' => $swarm->id, 'role' => $request->role]),
242248
'payload' => [
243249
'JoinSpec' => [
244250
'ListenAddr' => '0.0.0.0:2377',

app/Http/Requests/NodeTask/InitClusterFormRequest.php

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ public function rules(): array
1515
{
1616
return [
1717
'node_id' => ['required', 'exists:nodes,id'],
18-
'name' => ['required', 'string', 'max:255'],
1918
'advertise_addr' => ['required', 'ipv4'],
2019
'force_new_cluster' => ['boolean'],
2120
];

app/Http/Requests/NodeTask/JoinClusterFormRequest.php

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ public function rules(): array
1515
{
1616
return [
1717
'node_id' => ['required', 'exists:nodes,id'],
18-
'swarm_id' => ['required', 'exists:swarms,id'],
1918
'role' => ['required', 'in:manager,worker'],
2019
'advertise_addr' => ['exclude_if:role,worker', 'required', 'ipv4'],
2120
];

app/Http/Requests/StoreServiceRequest.php

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public function rules(): array
2323
{
2424
return [
2525
'name' => ['required', 'string'],
26-
'swarm_id' => ['required', 'exists:swarms,id'],
2726
];
2827
}
2928
}

app/Models/NodeTasks/InitSwarm/InitSwarmMeta.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ class InitSwarmMeta extends AbstractTaskMeta
88
{
99
public function __construct(
1010
public int $swarmId,
11-
public string $name,
1211
public bool $forceNewCluster
1312
) {}
1413

1514
public function formattedHtml(): string
1615
{
17-
$msg = 'Initialize Docker Swarm cluster <code>'.$this->name.'</code>';
16+
$msg = 'Initialize Docker Swarm cluster';
1817
if ($this->forceNewCluster) {
1918
$msg .= ' with <b>force cluster creation</b>';
2019
}

app/Models/Swarm.php

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ class Swarm extends Model
1313
use HasOwningTeam;
1414

1515
protected $fillable = [
16-
'name',
1716
'data',
1817
'team_id',
1918
];

app/Notifications/TrialEndsSoonNotification.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function toMail(object $notifiable): MailMessage
4545
$dateDiff = $nextPayment->date->longRelativeToNowDiffForHumans();
4646

4747
return (new MailMessage)
48-
->subject("Your trial ends in {$dateDiff}")
48+
->subject("Your free trial ends in {$dateDiff}")
4949
->greeting("Hello {$this->team->customer->name}!")
5050
->line('Your trial for team '.$this->team->name.' ends soon.')
5151
->line("You will be charged {$nextPayment->amount()} on {$nextPayment->date->toDateTimeString()} ({$dateDiff}).")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\DB;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
return new class extends Migration
9+
{
10+
/**
11+
* Run the migrations.
12+
*/
13+
public function up(): void
14+
{
15+
Schema::table('swarms', function (Blueprint $table) {
16+
$table->dropColumn('name');
17+
});
18+
}
19+
20+
/**
21+
* Reverse the migrations.
22+
*/
23+
public function down(): void
24+
{
25+
Schema::table('swarms', function (Blueprint $table) {
26+
$table->string('name')->after('id');
27+
});
28+
29+
DB::table('swarms')->update(['name' => DB::raw('CAST(id AS VARCHAR)')]);
30+
31+
Schema::table('swarms', function (Blueprint $table) {
32+
$table->string('name')->nullable(false)->change();
33+
});
34+
}
35+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<script setup>
2+
import { defineProps } from "vue";
3+
import { useForm } from "@inertiajs/vue3";
4+
import PrimaryButton from "@/Components/PrimaryButton.vue";
5+
import FormSection from "@/Components/FormSection.vue";
6+
import InputError from "@/Components/InputError.vue";
7+
import { FwbCheckbox } from "flowbite-vue";
8+
import Select from "@/Components/Select.vue";
9+
import FormField from "@/Components/FormField.vue";
10+
11+
const props = defineProps({
12+
node: Object,
13+
});
14+
15+
const initForm = useForm({
16+
node_id: props.node.id,
17+
advertise_addr: "",
18+
force_new_cluster: false,
19+
});
20+
21+
const submit = () => {
22+
initForm.post(route("swarm-tasks.init-cluster"), {
23+
preserveScroll: "errors",
24+
});
25+
};
26+
</script>
27+
28+
<template>
29+
<FormSection @submitted="submit">
30+
<template #title>Swarm Cluster</template>
31+
32+
<template #description>
33+
This is your first node in the swarm cluster. Initializing a new
34+
swarm cluster will make this node the manager node, responsible for
35+
orchestrating and managing the cluster. You can add worker nodes to
36+
this cluster later to distribute workloads efficiently.
37+
</template>
38+
39+
<template #form>
40+
<div v-auto-animate class="col-span-6 sm:col-span-4">
41+
<div class="grid gap-4">
42+
<template v-if="$props.node.data.host.networks.length > 0">
43+
<FormField :error="initForm.errors.advertise_addr">
44+
<template #label>Advertisement Address</template>
45+
<Select
46+
id="advertise_addr"
47+
v-model="initForm.advertise_addr"
48+
placeholder="Select Advertise Address"
49+
>
50+
<optgroup
51+
v-for="network in $props.node.data.host
52+
.networks"
53+
:label="network.if_name"
54+
>
55+
<option
56+
v-for="ip in network.ips"
57+
:value="ip.ip"
58+
>
59+
{{ ip.ip }}
60+
</option>
61+
</optgroup>
62+
</Select>
63+
</FormField>
64+
65+
<FormField :error="initForm.errors.force_new_cluster">
66+
<!-- TODO: add warning that the force new cluster will wipe out your existing cluster (services, data?) -->
67+
<FwbCheckbox
68+
label="Force New Cluster"
69+
v-model="initForm.force_new_cluster"
70+
/>
71+
</FormField>
72+
<InputError
73+
v-if="initForm.force_new_cluster"
74+
message="Be aware that you will lose your data!"
75+
/>
76+
</template>
77+
<template v-else> No IP found </template>
78+
</div>
79+
</div>
80+
</template>
81+
82+
<template #submit>
83+
<PrimaryButton type="submit"
84+
>Initialize Swarm Cluster</PrimaryButton
85+
>
86+
</template>
87+
</FormSection>
88+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<script setup>
2+
import { defineProps } from "vue";
3+
import { useForm } from "@inertiajs/vue3";
4+
import PrimaryButton from "@/Components/PrimaryButton.vue";
5+
import FormSection from "@/Components/FormSection.vue";
6+
import Select from "@/Components/Select.vue";
7+
import FormField from "@/Components/FormField.vue";
8+
9+
const props = defineProps({
10+
node: Object,
11+
});
12+
13+
const joinForm = useForm({
14+
node_id: props.node.id,
15+
role: "manager",
16+
advertise_addr: "",
17+
});
18+
19+
const submit = () => {
20+
joinForm.post(route("swarm-tasks.join-cluster"), {
21+
preserveScroll: "errors",
22+
});
23+
};
24+
</script>
25+
26+
<template>
27+
<FormSection @submitted="submit">
28+
<template #title>Swarm Cluster</template>
29+
30+
<template #description>
31+
Connect this node to an existing Docker Swarm cluster, enabling
32+
seamless integration and distributed computing capabilities.
33+
</template>
34+
35+
<template #form>
36+
<div class="col-span-6 sm:col-span-4">
37+
<div class="grid gap-4">
38+
<FormField :error="joinForm.errors.role">
39+
<template #label> Role </template>
40+
41+
<Select v-model="joinForm.role">
42+
<option value="manager">Manager</option>
43+
<option value="worker">Worker</option>
44+
</Select>
45+
</FormField>
46+
47+
<FormField :error="joinForm.errors.advertise_addr">
48+
<template #label>Advertisement Address</template>
49+
<Select
50+
id="advertise_addr"
51+
v-model="joinForm.advertise_addr"
52+
placeholder="Select Advertise Address"
53+
>
54+
<optgroup
55+
v-for="network in $props.node.data.host
56+
.networks"
57+
:label="network.if_name"
58+
>
59+
<option
60+
v-for="ip in network.ips"
61+
:value="ip.ip"
62+
>
63+
{{ ip.ip }}
64+
</option>
65+
</optgroup>
66+
</Select>
67+
</FormField>
68+
</div>
69+
</div>
70+
</template>
71+
72+
<template #submit>
73+
<PrimaryButton type="submit">Join Swarm Cluster</PrimaryButton>
74+
</template>
75+
</FormSection>
76+
</template>

0 commit comments

Comments
 (0)