diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..aec966f --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,3 @@ +# Add 'triage' label to any issue that gets opened +triage: + - '/.*/' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..bde5f72 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,18 @@ +name: "Issue Labeler" +on: + issues: + types: [opened] + +permissions: + issues: write + contents: read + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: github/issue-labeler@v3.4 + with: + configuration-path: .github/labeler.yml + enable-versioned-regex: 0 + repo-token: ${{ github.token }} diff --git a/app/HasOwningTeam.php b/app/HasOwningTeam.php new file mode 100644 index 0000000..464dc54 --- /dev/null +++ b/app/HasOwningTeam.php @@ -0,0 +1,23 @@ +team_id = auth()->user()->currentTeam->id; + }); + } + + public function team() + { + return $this->belongsTo(Team::class, 'team_id'); + } +} diff --git a/app/Http/Controllers/NodeController.php b/app/Http/Controllers/NodeController.php new file mode 100644 index 0000000..9cf2262 --- /dev/null +++ b/app/Http/Controllers/NodeController.php @@ -0,0 +1,69 @@ +all()); + + return to_route('nodes.show', ['node' => $node->id]); + } + + /** + * Display the specified resource. + */ + public function show(Node $node) + { + return Inertia::render('Nodes/Show', ['node' => $node]); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Node $node) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateNodeRequest $request, Node $node) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Node $node) + { + // + } +} diff --git a/app/Http/Controllers/SwarmController.php b/app/Http/Controllers/SwarmController.php new file mode 100644 index 0000000..57f1f2a --- /dev/null +++ b/app/Http/Controllers/SwarmController.php @@ -0,0 +1,66 @@ +header(self::AUTH_HEADER); + + if (!$token) { + return response()->json([ + 'message' => 'Unauthorized' + ], 401); + } + + $node = Node::whereAgentToken($token)->firstOrFail(); + + $node->last_seen_at = now(); + + $node->save(); + + return $next($request); + } +} diff --git a/app/Http/Requests/StoreNodeRequest.php b/app/Http/Requests/StoreNodeRequest.php new file mode 100644 index 0000000..9d23707 --- /dev/null +++ b/app/Http/Requests/StoreNodeRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'max:255'], + ]; + } +} diff --git a/app/Http/Requests/StoreSwarmRequest.php b/app/Http/Requests/StoreSwarmRequest.php new file mode 100644 index 0000000..8df9ac0 --- /dev/null +++ b/app/Http/Requests/StoreSwarmRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/UpdateNodeRequest.php b/app/Http/Requests/UpdateNodeRequest.php new file mode 100644 index 0000000..86ccda5 --- /dev/null +++ b/app/Http/Requests/UpdateNodeRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/UpdateSwarmRequest.php b/app/Http/Requests/UpdateSwarmRequest.php new file mode 100644 index 0000000..0831e1a --- /dev/null +++ b/app/Http/Requests/UpdateSwarmRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Models/Node.php b/app/Models/Node.php new file mode 100644 index 0000000..bbceaf6 --- /dev/null +++ b/app/Models/Node.php @@ -0,0 +1,35 @@ +agent_token = Str::random(42); + }); + } + + public function getOnlineAttribute() + { + return $this->last_seen_at > now()->subSeconds(35); + } +} diff --git a/app/Models/Scopes/TeamScope.php b/app/Models/Scopes/TeamScope.php new file mode 100644 index 0000000..6af67d5 --- /dev/null +++ b/app/Models/Scopes/TeamScope.php @@ -0,0 +1,18 @@ +where('team_id', auth()->user()->currentTeam->id); + } +} diff --git a/app/Models/Swarm.php b/app/Models/Swarm.php new file mode 100644 index 0000000..1a4f292 --- /dev/null +++ b/app/Models/Swarm.php @@ -0,0 +1,11 @@ +belongsToTeam($node->team); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return true; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Node $node): bool + { + return $user->belongsToTeam($node->team); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Node $node): bool + { + return $user->belongsToTeam($node->team); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Node $node): bool + { + return $user->belongsToTeam($node->team); + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Node $node): bool + { + return $user->belongsToTeam($node->team); + } +} diff --git a/app/Policies/SwarmPolicy.php b/app/Policies/SwarmPolicy.php new file mode 100644 index 0000000..622fde3 --- /dev/null +++ b/app/Policies/SwarmPolicy.php @@ -0,0 +1,66 @@ + + */ +class NodeFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/SwarmFactory.php b/database/factories/SwarmFactory.php new file mode 100644 index 0000000..83e9c92 --- /dev/null +++ b/database/factories/SwarmFactory.php @@ -0,0 +1,23 @@ + + */ +class SwarmFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/migrations/2024_06_19_084206_create_swarms_table.php b/database/migrations/2024_06_19_084206_create_swarms_table.php new file mode 100644 index 0000000..67033a1 --- /dev/null +++ b/database/migrations/2024_06_19_084206_create_swarms_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + $table->string('name'); + $table->string('ext_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('swarms'); + } +}; diff --git a/database/migrations/2024_06_19_084441_create_nodes_table.php b/database/migrations/2024_06_19_084441_create_nodes_table.php new file mode 100644 index 0000000..a97db34 --- /dev/null +++ b/database/migrations/2024_06_19_084441_create_nodes_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + $table->string('name'); + $table->string('agent_token')->unique(); + $table->string('ext_id')->nullable(); + $table->timestamp('last_seen_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('nodes'); + } +}; diff --git a/package-lock.json b/package-lock.json index 25ee32c..4038257 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "devDependencies": { + "@formkit/auto-animate": "^0.8.2", "@inertiajs/vue3": "^1.0.14", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", @@ -12,6 +13,7 @@ "@vue/server-renderer": "^3.3.13", "autoprefixer": "^10.4.16", "axios": "^1.6.4", + "flowbite": "^2.3.0", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", @@ -411,6 +413,12 @@ "node": ">=12" } }, + "node_modules/@formkit/auto-animate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.2.tgz", + "integrity": "sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ==", + "dev": true + }, "node_modules/@inertiajs/core": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-1.2.0.tgz", @@ -547,6 +555,16 @@ "node": ">=14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -1430,6 +1448,16 @@ "node": ">=8" } }, + "node_modules/flowbite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.3.0.tgz", + "integrity": "sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.3", + "mini-svg-data-uri": "^1.4.3" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", diff --git a/package.json b/package.json index ed8d8b7..80853ae 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "build": "vite build && vite build --ssr" }, "devDependencies": { + "@formkit/auto-animate": "^0.8.2", "@inertiajs/vue3": "^1.0.14", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", @@ -13,6 +14,7 @@ "@vue/server-renderer": "^3.3.13", "autoprefixer": "^10.4.16", "axios": "^1.6.4", + "flowbite": "^2.3.0", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", diff --git a/resources/js/Components/InputError.vue b/resources/js/Components/InputError.vue index 47ddb27..a848bdc 100644 --- a/resources/js/Components/InputError.vue +++ b/resources/js/Components/InputError.vue @@ -5,7 +5,7 @@ defineProps({