feat: add restart server action for vendor and tenant
Some checks failed
CI / build (push) Failing after 36s
CI / docker (push) Has been skipped

Vendor: POST /api/vendor/tenants/{id}/restart (platform:admin scope)
Tenant: POST /api/tenant/server/restart (tenant:manage scope)

Both call TenantProvisioner.stop() then start() on the server + UI
containers. Restart button on vendor TenantDetailPage (Actions card)
and tenant TenantDashboardPage (Server card). Allowed in any status
including PROVISIONING.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-10 17:21:14 +02:00
parent 252c18bcff
commit 7e7a07470b
8 changed files with 96 additions and 3 deletions

View File

@@ -70,6 +70,12 @@ public class TenantPortalController {
return ResponseEntity.ok().build();
}
@PostMapping("/server/restart")
public ResponseEntity<Void> restartServer() {
portalService.restartServer();
return ResponseEntity.ok().build();
}
@GetMapping("/settings")
public ResponseEntity<TenantPortalService.TenantSettingsData> getSettings() {
return ResponseEntity.ok(portalService.getSettings());

View File

@@ -5,6 +5,7 @@ import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.identity.ServerApiClient;
import net.siegeln.cameleer.saas.license.LicenseEntity;
import net.siegeln.cameleer.saas.license.LicenseService;
import net.siegeln.cameleer.saas.provisioning.TenantProvisioner;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.TenantService;
import org.springframework.stereotype.Service;
@@ -22,15 +23,18 @@ public class TenantPortalService {
private final LicenseService licenseService;
private final ServerApiClient serverApiClient;
private final LogtoManagementClient logtoClient;
private final TenantProvisioner tenantProvisioner;
public TenantPortalService(TenantService tenantService,
LicenseService licenseService,
ServerApiClient serverApiClient,
LogtoManagementClient logtoClient) {
LogtoManagementClient logtoClient,
TenantProvisioner tenantProvisioner) {
this.tenantService = tenantService;
this.licenseService = licenseService;
this.serverApiClient = serverApiClient;
this.logtoClient = logtoClient;
this.tenantProvisioner = tenantProvisioner;
}
// --- Inner records ---
@@ -160,4 +164,12 @@ public class TenantPortalService {
tenant.getServerEndpoint(), tenant.getCreatedAt()
);
}
public void restartServer() {
TenantEntity tenant = resolveTenant();
if (tenantProvisioner.isAvailable()) {
tenantProvisioner.stop(tenant.getSlug());
tenantProvisioner.start(tenant.getSlug());
}
}
}

View File

@@ -109,6 +109,16 @@ public class VendorTenantController {
.orElse(ResponseEntity.notFound().build());
}
@PostMapping("/{id}/restart")
public ResponseEntity<Void> restart(@PathVariable UUID id) {
try {
vendorTenantService.restartServer(id);
return ResponseEntity.ok().build();
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
}
}
@PostMapping("/{id}/suspend")
public ResponseEntity<TenantResponse> suspend(@PathVariable UUID id,
@AuthenticationPrincipal Jwt jwt) {

View File

@@ -213,6 +213,15 @@ public class VendorTenantService {
return serverApiClient.getHealth(endpoint);
}
public void restartServer(UUID tenantId) {
TenantEntity tenant = tenantService.getById(tenantId)
.orElseThrow(() -> new IllegalArgumentException("Tenant not found"));
if (tenantProvisioner.isAvailable()) {
tenantProvisioner.stop(tenant.getSlug());
tenantProvisioner.start(tenant.getSlug());
}
}
@Transactional
public TenantEntity suspend(UUID tenantId, UUID actorId) {
TenantEntity tenant = tenantService.getById(tenantId)