diff --git a/app/Console/Commands/GenerateJSEnums.php b/app/Console/Commands/GenerateJSEnums.php
new file mode 100644
index 0000000..88fa0a4
--- /dev/null
+++ b/app/Console/Commands/GenerateJSEnums.php
@@ -0,0 +1,90 @@
+load($enums);
+
+ return 0;
+ }
+
+ protected function load($paths)
+ {
+ $paths = array_unique(Arr::wrap($paths));
+
+ $paths = array_filter($paths, function ($path) {
+ return is_dir($path);
+ });
+
+ if (empty($paths)) {
+ return;
+ }
+
+ foreach ((new Finder)->in($paths)->files() as $enum) {
+ $enum = 'App\\' . str_replace(
+ ['/', '.php'],
+ ['\\', ''],
+ Str::after($enum->getRealPath(), realpath(app_path()) . DIRECTORY_SEPARATOR)
+ );
+
+ if (! class_exists($enum)) {
+ continue;
+ }
+
+ $js = "// This is a generated file. \n";
+ $js .= '// Published at ' . now()->format('Y-m-d H:i:s') . "\n";
+ $js .= "\n";
+ $js .= 'export default ';
+ $js .= json_encode($enum::toArray(), JSON_PRETTY_PRINT) . "\n";
+ $js .= "\n";
+
+ if (method_exists($enum, 'getLabels')) {
+ $labels = $enum::getLabels();
+ $js .= 'export const LabelMap = ';
+ $js .= json_encode($labels, JSON_PRETTY_PRINT) . "\n";
+ $js .= "\n";
+
+ $labelSelect = array_map(fn($key) => ['title' => $labels[$key], 'id' => $key], array_keys($labels));
+ $js .= 'export const LabelSelectMap = ';
+ $js .= json_encode($labelSelect, JSON_PRETTY_PRINT) . "\n";
+ $js .= "\n";
+ }
+
+ if (method_exists($enum, 'colours')) {
+ $colours = $enum::colours();
+ $js .= 'export const ColourMap = ';
+ $js .= json_encode($colours, JSON_PRETTY_PRINT) . "\n";
+ $js .= "\n";
+ }
+
+ $name = explode('\\', $enum)[count(explode('\\', $enum)) - 1];
+
+ // Skip format, JS date formats are different to PHP ones.
+ if ($name !== 'Format') {
+ file_put_contents(base_path('resources/js/Enums/' . $name . '.js'), $js);
+ $this->info('Stored ' . $enum);
+ } else {
+ $this->info('Skipped ' . $name . 's');
+ }
+ }
+ }
+}
diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php
new file mode 100644
index 0000000..63653e7
--- /dev/null
+++ b/app/Http/Controllers/ServiceController.php
@@ -0,0 +1,16 @@
+route('server');
+ return inertia('services/Create', [
+ 'server' => $server,
+ ]);
+ }
+}
diff --git a/resources/js/enums/DeploymentStatus.js b/resources/js/enums/DeploymentStatus.js
new file mode 100644
index 0000000..8159a5a
--- /dev/null
+++ b/resources/js/enums/DeploymentStatus.js
@@ -0,0 +1,11 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "PENDING": "pending",
+ "IN_PROGRESS": "in-progress",
+ "COMPLETED": "completed",
+ "CANCELLED": "canceled",
+ "FAILED": "failed"
+}
+
diff --git a/resources/js/enums/FirewallRuleStatus.js b/resources/js/enums/FirewallRuleStatus.js
new file mode 100644
index 0000000..1cadfaa
--- /dev/null
+++ b/resources/js/enums/FirewallRuleStatus.js
@@ -0,0 +1,9 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "NOT_APPLIED": "not-applied",
+ "APPLIED": "applied",
+ "FAILED": "failed"
+}
+
diff --git a/resources/js/enums/OrganisationRole.js b/resources/js/enums/OrganisationRole.js
new file mode 100644
index 0000000..b19af7d
--- /dev/null
+++ b/resources/js/enums/OrganisationRole.js
@@ -0,0 +1,8 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "ADMIN": "admin",
+ "MEMBER": "member"
+}
+
diff --git a/resources/js/enums/RepositoryType.js b/resources/js/enums/RepositoryType.js
new file mode 100644
index 0000000..887413f
--- /dev/null
+++ b/resources/js/enums/RepositoryType.js
@@ -0,0 +1,7 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "GIT": "git"
+}
+
diff --git a/resources/js/enums/ServerProvider.js b/resources/js/enums/ServerProvider.js
new file mode 100644
index 0000000..5ee2b56
--- /dev/null
+++ b/resources/js/enums/ServerProvider.js
@@ -0,0 +1,8 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "HETZNER": "hetzner",
+ "DIGITAL_OCEAN": "digital-ocean"
+}
+
diff --git a/resources/js/enums/ServerStatus.js b/resources/js/enums/ServerStatus.js
new file mode 100644
index 0000000..c014333
--- /dev/null
+++ b/resources/js/enums/ServerStatus.js
@@ -0,0 +1,15 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "WAITING_FOR_PROVIDER": "waiting-for-provider",
+ "PROVIDER_TIMEOUT": "provider-timeout",
+ "UNPROVISIONED": "unprovisioned",
+ "PROVISIONING": "provisioning",
+ "PROVISIONING_FAILED": "provisioning-failed",
+ "UPDATING": "updating",
+ "ACTIVE": "active",
+ "DELETING": "deleting",
+ "DELETED": "deleted"
+}
+
diff --git a/resources/js/enums/ServiceCategory.js b/resources/js/enums/ServiceCategory.js
new file mode 100644
index 0000000..f4adf42
--- /dev/null
+++ b/resources/js/enums/ServiceCategory.js
@@ -0,0 +1,11 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "DATABASE": "database",
+ "APPLICATION": "application",
+ "GATEWAY": "gateway",
+ "STORAGE": "storage",
+ "CACHE": "cache"
+}
+
diff --git a/resources/js/enums/ServiceStatus.js b/resources/js/enums/ServiceStatus.js
new file mode 100644
index 0000000..cbf1278
--- /dev/null
+++ b/resources/js/enums/ServiceStatus.js
@@ -0,0 +1,12 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "NOT_INSTALLED": "not-installed",
+ "INSTALLING": "installing",
+ "RUNNING": "running",
+ "STOPPED": "stopped",
+ "ERROR": "error",
+ "UNKNOWN": "unknown"
+}
+
diff --git a/resources/js/enums/ServiceType.js b/resources/js/enums/ServiceType.js
new file mode 100644
index 0000000..a556c6f
--- /dev/null
+++ b/resources/js/enums/ServiceType.js
@@ -0,0 +1,14 @@
+// This is a generated file.
+// Published at 2025-04-01 16:18:32
+
+export default {
+ "FRANKENPHP": "frankenphp",
+ "PHP_FPM": "php-fpm",
+ "POSTGRES": "postgres",
+ "CADDY": "caddy",
+ "VALKEY": "valkey",
+ "MYSQL": "mysql",
+ "NGINX": "nginx",
+ "REDIS": "redis"
+}
+
diff --git a/resources/js/pages/servers/Show.vue b/resources/js/pages/servers/Show.vue
index 5a6f6f9..f82d048 100644
--- a/resources/js/pages/servers/Show.vue
+++ b/resources/js/pages/servers/Show.vue
@@ -1,10 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/routes/web.php b/routes/web.php
index 1a960cb..1200f4d 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -5,6 +5,7 @@ use App\Http\Controllers\ApplicationController;
use App\Http\Controllers\EnvironmentController;
use App\Http\Controllers\OrganisationController;
use App\Http\Controllers\ServerController;
+use App\Http\Controllers\ServiceController;
use App\Models\Server;
use App\Support\Ip;
use Illuminate\Http\Request;
@@ -27,6 +28,13 @@ Route::middleware(['auth', 'verified'])->group(function () {
->name('show', 'servers.show')
->name('create', 'servers.create')
->name('store', 'servers.store');
+
+ Route::prefix('servers/{server}')->group(function () {
+ Route::resource('services', ServiceController::class)
+ ->only('create', 'store')
+ ->name('create', 'services.create')
+ ->name('store', 'services.store');
+ });
Route::resource('applications', ApplicationController::class)
->only('show')