Add customer dashboard, provisioning services, and legal pages

Phase 3 (Provisioning):
- Create ProvisioningServiceInterface abstraction
- Implement VirtFusionService for VPS provisioning via REST API
- Implement SynergyCPService for dedicated server provisioning
- Implement EnhanceService for web hosting provisioning
- Create ProvisioningFactory for service type routing
- Add API config for VirtFusion, SynergyCP, Enhance

Phase 4 (Customer Dashboard):
- Enhance DashboardController with real data (services, subscriptions,
  invoices, pending amounts, next renewal)
- Rebuild Dashboard.vue with stats cards, active subscriptions list,
  recent invoices table, and quick action buttons

Phase 8 (Marketing):
- Add Terms of Service page with 15 sections
- Add Privacy Policy page (GDPR/CCPA aware)
- Add Acceptable Use Policy page
- Add Service Level Agreement page (99.99% uptime, credit calc)
- 52 tests passing, build clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Dev
2026-02-09 10:21:25 -05:00
parent ec8f0272ec
commit 33e86a32a8
14 changed files with 2728 additions and 35 deletions

View File

@@ -15,9 +15,40 @@ class DashboardController extends Controller
{ {
$user = $request->user(); $user = $request->user();
$activeServicesCount = $user->services()
->where('status', 'active')
->count();
$activeSubscriptions = $user->subscriptions()
->with('plan')
->whereIn('stripe_status', ['active', 'trialing'])
->latest()
->get();
$activeSubscriptionsCount = $activeSubscriptions->count();
$latestInvoices = $user->invoices()
->latest()
->limit(5)
->get();
$pendingInvoicesAmount = $user->invoices()
->whereIn('status', ['pending', 'overdue'])
->sum('total');
$nextRenewalDate = $user->subscriptions()
->whereIn('stripe_status', ['active', 'trialing'])
->whereNotNull('current_period_end')
->orderBy('current_period_end')
->value('current_period_end');
return Inertia::render('Dashboard', [ return Inertia::render('Dashboard', [
'servicesCount' => $user->services()->count(), 'activeServicesCount' => $activeServicesCount,
'activeServicesCount' => $user->services()->where('status', 'active')->count(), 'activeSubscriptionsCount' => $activeSubscriptionsCount,
'activeSubscriptions' => $activeSubscriptions,
'latestInvoices' => $latestInvoices,
'pendingInvoicesAmount' => number_format((float) $pendingInvoicesAmount, 2, '.', ''),
'nextRenewalDate' => $nextRenewalDate,
]); ]);
} }
} }

View File

@@ -7,6 +7,7 @@ namespace App\Providers;
use App\Http\Responses\LoginResponse; use App\Http\Responses\LoginResponse;
use App\Http\Responses\RegisterResponse; use App\Http\Responses\RegisterResponse;
use App\Services\Billing\BillingServiceFactory; use App\Services\Billing\BillingServiceFactory;
use App\Services\Provisioning\ProvisioningFactory;
use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
@@ -22,6 +23,7 @@ class AppServiceProvider extends ServiceProvider
$this->app->singleton(LoginResponseContract::class, LoginResponse::class); $this->app->singleton(LoginResponseContract::class, LoginResponse::class);
$this->app->singleton(RegisterResponseContract::class, RegisterResponse::class); $this->app->singleton(RegisterResponseContract::class, RegisterResponse::class);
$this->app->singleton(BillingServiceFactory::class); $this->app->singleton(BillingServiceFactory::class);
$this->app->singleton(ProvisioningFactory::class);
} }
public function boot(): void public function boot(): void

View File

@@ -0,0 +1,298 @@
<?php
declare(strict_types=1);
namespace App\Services\Provisioning;
use App\Models\ProvisioningLog;
use App\Models\Service;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Subscription;
use RuntimeException;
class EnhanceService implements ProvisioningServiceInterface
{
private readonly string $baseUrl;
private readonly string $token;
public function __construct()
{
$this->baseUrl = rtrim(config('services.enhance.url', ''), '/');
$this->token = config('services.enhance.token', '');
}
public function provision(Subscription $subscription): Service
{
$plan = $subscription->plan;
$user = $subscription->user;
if (! $plan) {
throw new RuntimeException('Subscription has no associated plan.');
}
$service = Service::create([
'user_id' => $user->id,
'subscription_id' => $subscription->id,
'plan_id' => $plan->id,
'service_type' => 'hosting',
'platform' => 'enhance',
'status' => 'pending',
]);
$this->logAction($service, 'provision', 'pending');
try {
$response = $this->client()->post('/api/v1/websites', [
'plan_id' => $plan->features['enhance_plan_id'] ?? null,
'domain' => $plan->features['default_domain'] ?? 'hosting.ezscale.cloud',
'customer_email' => $user->email,
]);
if (! $response->successful()) {
$this->logAction($service, 'provision', 'failed', $response->json(), $response->body());
throw new RuntimeException("Enhance provisioning failed: {$response->body()}");
}
$data = $response->json();
$websiteId = (string) ($data['data']['id'] ?? $data['id'] ?? '');
$service->update([
'platform_service_id' => $websiteId,
'status' => 'active',
'domain' => $data['data']['domain'] ?? $data['domain'] ?? null,
'ipv4_address' => $data['data']['ip_address'] ?? $data['ip_address'] ?? null,
'provisioned_at' => now(),
]);
$this->logAction($service, 'provision', 'success', $data);
return $service->fresh();
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $e) {
$this->logAction($service, 'provision', 'failed', errorMessage: $e->getMessage());
$service->update(['status' => 'failed']);
throw new RuntimeException("Enhance provisioning error: {$e->getMessage()}", 0, $e);
}
}
public function suspend(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'suspend', 'pending');
try {
$response = $this->client()->post("/api/v1/websites/{$service->platform_service_id}/suspend");
if (! $response->successful()) {
$this->logAction($service, 'suspend', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'suspended',
'suspended_at' => now(),
]);
$this->logAction($service, 'suspend', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('Enhance suspend failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'suspend', 'failed', errorMessage: $e->getMessage());
return false;
}
}
public function unsuspend(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'unsuspend', 'pending');
try {
$response = $this->client()->post("/api/v1/websites/{$service->platform_service_id}/unsuspend");
if (! $response->successful()) {
$this->logAction($service, 'unsuspend', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'active',
'suspended_at' => null,
]);
$this->logAction($service, 'unsuspend', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('Enhance unsuspend failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'unsuspend', 'failed', errorMessage: $e->getMessage());
return false;
}
}
public function terminate(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'terminate', 'pending');
try {
$response = $this->client()->delete("/api/v1/websites/{$service->platform_service_id}");
if (! $response->successful()) {
$this->logAction($service, 'terminate', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'terminated',
'terminated_at' => now(),
]);
$this->logAction($service, 'terminate', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('Enhance terminate failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'terminate', 'failed', errorMessage: $e->getMessage());
return false;
}
}
/**
* @return array{status: string, ip_address?: string, hostname?: string, platform_data?: array<string, mixed>}
*/
public function getStatus(Service $service): array
{
$this->validateServicePlatform($service);
try {
$response = $this->client()->get("/api/v1/websites/{$service->platform_service_id}");
if (! $response->successful()) {
return ['status' => 'unknown'];
}
$data = $response->json();
$websiteData = $data['data'] ?? $data;
return [
'status' => $websiteData['status'] ?? 'unknown',
'ip_address' => $websiteData['ip_address'] ?? $service->ipv4_address,
'hostname' => $websiteData['domain'] ?? $service->domain,
'platform_data' => $websiteData,
];
} catch (\Exception $e) {
Log::error('Enhance getStatus failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
return ['status' => 'unknown'];
}
}
/**
* @return array{username?: string, password?: string, url?: string, additional?: array<string, mixed>}
*/
public function getCredentials(Service $service): array
{
$this->validateServicePlatform($service);
$stored = $service->credentials ?? [];
try {
$response = $this->client()->get("/api/v1/websites/{$service->platform_service_id}");
if ($response->successful()) {
$data = $response->json();
$websiteData = $data['data'] ?? $data;
return [
'username' => $stored['username'] ?? null,
'password' => $stored['password'] ?? null,
'url' => $websiteData['control_panel_url'] ?? null,
'additional' => [
'domain' => $websiteData['domain'] ?? $service->domain,
'ip_address' => $websiteData['ip_address'] ?? $service->ipv4_address,
'nameservers' => $websiteData['nameservers'] ?? [],
],
];
}
} catch (\Exception $e) {
Log::error('Enhance getCredentials failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
}
return [
'username' => $stored['username'] ?? null,
'password' => $stored['password'] ?? null,
];
}
private function client(): PendingRequest
{
return Http::withToken($this->token)
->baseUrl($this->baseUrl)
->acceptJson()
->timeout(30);
}
private function validateServicePlatform(Service $service): void
{
if (! $service->platform_service_id) {
throw new RuntimeException('Service has no platform service ID.');
}
}
/**
* @param array<string, mixed>|null $response
*/
private function logAction(
Service $service,
string $action,
string $status,
?array $response = null,
?string $errorMessage = null,
): void {
ProvisioningLog::create([
'service_id' => $service->id,
'user_id' => $service->user_id,
'action' => $action,
'platform' => 'enhance',
'platform_response' => $response,
'status' => $status,
'error_message' => $errorMessage,
]);
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Services\Provisioning;
use InvalidArgumentException;
class ProvisioningFactory
{
/**
* Create a provisioning service instance based on service type.
*/
public function make(string $serviceType): ProvisioningServiceInterface
{
return match ($serviceType) {
'vps' => app(VirtFusionService::class),
'dedicated' => app(SynergyCPService::class),
'hosting' => app(EnhanceService::class),
default => throw new InvalidArgumentException("Unsupported service type: {$serviceType}"),
};
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Services\Provisioning;
use App\Models\Service;
use Laravel\Cashier\Subscription;
interface ProvisioningServiceInterface
{
/**
* Provision a new service for a subscription.
*/
public function provision(Subscription $subscription): Service;
/**
* Suspend an active service.
*/
public function suspend(Service $service): bool;
/**
* Unsuspend a previously suspended service.
*/
public function unsuspend(Service $service): bool;
/**
* Terminate and permanently remove a service.
*/
public function terminate(Service $service): bool;
/**
* Get the current status of a service from the platform.
*
* @return array{status: string, ip_address?: string, hostname?: string, platform_data?: array<string, mixed>}
*/
public function getStatus(Service $service): array;
/**
* Get the access credentials for a service.
*
* @return array{username?: string, password?: string, url?: string, additional?: array<string, mixed>}
*/
public function getCredentials(Service $service): array;
}

View File

@@ -0,0 +1,298 @@
<?php
declare(strict_types=1);
namespace App\Services\Provisioning;
use App\Models\ProvisioningLog;
use App\Models\Service;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Subscription;
use RuntimeException;
class SynergyCPService implements ProvisioningServiceInterface
{
private readonly string $baseUrl;
private readonly string $token;
public function __construct()
{
$this->baseUrl = rtrim(config('services.synergycp.url', ''), '/');
$this->token = config('services.synergycp.token', '');
}
public function provision(Subscription $subscription): Service
{
$plan = $subscription->plan;
$user = $subscription->user;
if (! $plan) {
throw new RuntimeException('Subscription has no associated plan.');
}
$service = Service::create([
'user_id' => $user->id,
'subscription_id' => $subscription->id,
'plan_id' => $plan->id,
'service_type' => 'dedicated',
'platform' => 'synergycp',
'status' => 'pending',
]);
$this->logAction($service, 'provision', 'pending');
try {
$response = $this->client()->post('/api/v1/servers', [
'plan_id' => $plan->features['synergycp_plan_id'] ?? null,
'client_email' => $user->email,
'hostname' => $plan->features['default_hostname'] ?? 'dedicated.ezscale.cloud',
]);
if (! $response->successful()) {
$this->logAction($service, 'provision', 'failed', $response->json(), $response->body());
throw new RuntimeException("SynergyCP provisioning failed: {$response->body()}");
}
$data = $response->json();
$serverId = (string) ($data['data']['id'] ?? $data['id'] ?? '');
$service->update([
'platform_service_id' => $serverId,
'status' => 'active',
'ipv4_address' => $data['data']['ip_address'] ?? $data['ip_address'] ?? null,
'hostname' => $data['data']['hostname'] ?? $data['hostname'] ?? null,
'provisioned_at' => now(),
]);
$this->logAction($service, 'provision', 'success', $data);
return $service->fresh();
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $e) {
$this->logAction($service, 'provision', 'failed', errorMessage: $e->getMessage());
$service->update(['status' => 'failed']);
throw new RuntimeException("SynergyCP provisioning error: {$e->getMessage()}", 0, $e);
}
}
public function suspend(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'suspend', 'pending');
try {
$response = $this->client()->post("/api/v1/servers/{$service->platform_service_id}/suspend");
if (! $response->successful()) {
$this->logAction($service, 'suspend', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'suspended',
'suspended_at' => now(),
]);
$this->logAction($service, 'suspend', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('SynergyCP suspend failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'suspend', 'failed', errorMessage: $e->getMessage());
return false;
}
}
public function unsuspend(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'unsuspend', 'pending');
try {
$response = $this->client()->post("/api/v1/servers/{$service->platform_service_id}/unsuspend");
if (! $response->successful()) {
$this->logAction($service, 'unsuspend', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'active',
'suspended_at' => null,
]);
$this->logAction($service, 'unsuspend', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('SynergyCP unsuspend failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'unsuspend', 'failed', errorMessage: $e->getMessage());
return false;
}
}
public function terminate(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'terminate', 'pending');
try {
$response = $this->client()->delete("/api/v1/servers/{$service->platform_service_id}");
if (! $response->successful()) {
$this->logAction($service, 'terminate', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'terminated',
'terminated_at' => now(),
]);
$this->logAction($service, 'terminate', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('SynergyCP terminate failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'terminate', 'failed', errorMessage: $e->getMessage());
return false;
}
}
/**
* @return array{status: string, ip_address?: string, hostname?: string, platform_data?: array<string, mixed>}
*/
public function getStatus(Service $service): array
{
$this->validateServicePlatform($service);
try {
$response = $this->client()->get("/api/v1/servers/{$service->platform_service_id}");
if (! $response->successful()) {
return ['status' => 'unknown'];
}
$data = $response->json();
$serverData = $data['data'] ?? $data;
return [
'status' => $serverData['status'] ?? 'unknown',
'ip_address' => $serverData['ip_address'] ?? $service->ipv4_address,
'hostname' => $serverData['hostname'] ?? $service->hostname,
'platform_data' => $serverData,
];
} catch (\Exception $e) {
Log::error('SynergyCP getStatus failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
return ['status' => 'unknown'];
}
}
/**
* @return array{username?: string, password?: string, url?: string, additional?: array<string, mixed>}
*/
public function getCredentials(Service $service): array
{
$this->validateServicePlatform($service);
$stored = $service->credentials ?? [];
try {
$response = $this->client()->get("/api/v1/servers/{$service->platform_service_id}");
if ($response->successful()) {
$data = $response->json();
$serverData = $data['data'] ?? $data;
return [
'username' => $stored['username'] ?? 'root',
'password' => $stored['password'] ?? null,
'url' => $serverData['ipmi_url'] ?? null,
'additional' => [
'ip_address' => $serverData['ip_address'] ?? $service->ipv4_address,
'hostname' => $serverData['hostname'] ?? $service->hostname,
'ipmi_ip' => $serverData['ipmi_ip'] ?? null,
],
];
}
} catch (\Exception $e) {
Log::error('SynergyCP getCredentials failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
}
return [
'username' => $stored['username'] ?? 'root',
'password' => $stored['password'] ?? null,
];
}
private function client(): PendingRequest
{
return Http::withToken($this->token)
->baseUrl($this->baseUrl)
->acceptJson()
->timeout(30);
}
private function validateServicePlatform(Service $service): void
{
if (! $service->platform_service_id) {
throw new RuntimeException('Service has no platform service ID.');
}
}
/**
* @param array<string, mixed>|null $response
*/
private function logAction(
Service $service,
string $action,
string $status,
?array $response = null,
?string $errorMessage = null,
): void {
ProvisioningLog::create([
'service_id' => $service->id,
'user_id' => $service->user_id,
'action' => $action,
'platform' => 'synergycp',
'platform_response' => $response,
'status' => $status,
'error_message' => $errorMessage,
]);
}
}

View File

@@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
namespace App\Services\Provisioning;
use App\Models\ProvisioningLog;
use App\Models\Service;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Subscription;
use RuntimeException;
class VirtFusionService implements ProvisioningServiceInterface
{
private readonly string $baseUrl;
private readonly string $token;
public function __construct()
{
$this->baseUrl = rtrim(config('services.virtfusion.url', ''), '/');
$this->token = config('services.virtfusion.token', '');
}
public function provision(Subscription $subscription): Service
{
$plan = $subscription->plan;
$user = $subscription->user;
if (! $plan) {
throw new RuntimeException('Subscription has no associated plan.');
}
$service = Service::create([
'user_id' => $user->id,
'subscription_id' => $subscription->id,
'plan_id' => $plan->id,
'service_type' => 'vps',
'platform' => 'virtfusion',
'status' => 'pending',
]);
$this->logAction($service, 'provision', 'pending');
try {
$response = $this->client()->post('/api/v1/servers', [
'package_id' => $plan->features['virtfusion_package_id'] ?? null,
'user_email' => $user->email,
'hostname' => $plan->features['default_hostname'] ?? 'server.ezscale.cloud',
]);
if (! $response->successful()) {
$this->logAction($service, 'provision', 'failed', $response->json(), $response->body());
throw new RuntimeException("VirtFusion provisioning failed: {$response->body()}");
}
$data = $response->json();
$serverId = (string) ($data['data']['id'] ?? $data['id'] ?? '');
$service->update([
'platform_service_id' => $serverId,
'status' => 'active',
'ipv4_address' => $data['data']['ip_address'] ?? $data['ip_address'] ?? null,
'hostname' => $data['data']['hostname'] ?? $data['hostname'] ?? null,
'provisioned_at' => now(),
]);
$this->logAction($service, 'provision', 'success', $data);
return $service->fresh();
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $e) {
$this->logAction($service, 'provision', 'failed', errorMessage: $e->getMessage());
$service->update(['status' => 'failed']);
throw new RuntimeException("VirtFusion provisioning error: {$e->getMessage()}", 0, $e);
}
}
public function suspend(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'suspend', 'pending');
try {
$response = $this->client()->post("/api/v1/servers/{$service->platform_service_id}/suspend");
if (! $response->successful()) {
$this->logAction($service, 'suspend', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'suspended',
'suspended_at' => now(),
]);
$this->logAction($service, 'suspend', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('VirtFusion suspend failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'suspend', 'failed', errorMessage: $e->getMessage());
return false;
}
}
public function unsuspend(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'unsuspend', 'pending');
try {
$response = $this->client()->post("/api/v1/servers/{$service->platform_service_id}/unsuspend");
if (! $response->successful()) {
$this->logAction($service, 'unsuspend', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'active',
'suspended_at' => null,
]);
$this->logAction($service, 'unsuspend', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('VirtFusion unsuspend failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'unsuspend', 'failed', errorMessage: $e->getMessage());
return false;
}
}
public function terminate(Service $service): bool
{
$this->validateServicePlatform($service);
$this->logAction($service, 'terminate', 'pending');
try {
$response = $this->client()->delete("/api/v1/servers/{$service->platform_service_id}");
if (! $response->successful()) {
$this->logAction($service, 'terminate', 'failed', $response->json(), $response->body());
return false;
}
$service->update([
'status' => 'terminated',
'terminated_at' => now(),
]);
$this->logAction($service, 'terminate', 'success', $response->json());
return true;
} catch (\Exception $e) {
Log::error('VirtFusion terminate failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
$this->logAction($service, 'terminate', 'failed', errorMessage: $e->getMessage());
return false;
}
}
/**
* @return array{status: string, ip_address?: string, hostname?: string, platform_data?: array<string, mixed>}
*/
public function getStatus(Service $service): array
{
$this->validateServicePlatform($service);
try {
$response = $this->client()->get("/api/v1/servers/{$service->platform_service_id}");
if (! $response->successful()) {
return ['status' => 'unknown'];
}
$data = $response->json();
$serverData = $data['data'] ?? $data;
return [
'status' => $serverData['status'] ?? 'unknown',
'ip_address' => $serverData['ip_address'] ?? $service->ipv4_address,
'hostname' => $serverData['hostname'] ?? $service->hostname,
'platform_data' => $serverData,
];
} catch (\Exception $e) {
Log::error('VirtFusion getStatus failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
return ['status' => 'unknown'];
}
}
/**
* @return array{username?: string, password?: string, url?: string, additional?: array<string, mixed>}
*/
public function getCredentials(Service $service): array
{
$this->validateServicePlatform($service);
$stored = $service->credentials ?? [];
try {
$response = $this->client()->get("/api/v1/servers/{$service->platform_service_id}");
if ($response->successful()) {
$data = $response->json();
$serverData = $data['data'] ?? $data;
return [
'username' => $stored['username'] ?? 'root',
'password' => $stored['password'] ?? null,
'url' => $serverData['vnc_url'] ?? null,
'additional' => [
'ip_address' => $serverData['ip_address'] ?? $service->ipv4_address,
'hostname' => $serverData['hostname'] ?? $service->hostname,
],
];
}
} catch (\Exception $e) {
Log::error('VirtFusion getCredentials failed', [
'service_id' => $service->id,
'error' => $e->getMessage(),
]);
}
return [
'username' => $stored['username'] ?? 'root',
'password' => $stored['password'] ?? null,
];
}
private function client(): PendingRequest
{
return Http::withToken($this->token)
->baseUrl($this->baseUrl)
->acceptJson()
->timeout(30);
}
private function validateServicePlatform(Service $service): void
{
if (! $service->platform_service_id) {
throw new RuntimeException('Service has no platform service ID.');
}
}
/**
* @param array<string, mixed>|null $response
*/
private function logAction(
Service $service,
string $action,
string $status,
?array $response = null,
?string $errorMessage = null,
): void {
ProvisioningLog::create([
'service_id' => $service->id,
'user_id' => $service->user_id,
'action' => $action,
'platform' => 'virtfusion',
'platform_response' => $response,
'status' => $status,
'error_message' => $errorMessage,
]);
}
}

View File

@@ -35,4 +35,19 @@ return [
], ],
], ],
'virtfusion' => [
'url' => env('VIRTFUSION_API_URL'),
'token' => env('VIRTFUSION_API_TOKEN'),
],
'synergycp' => [
'url' => env('SYNERGYCP_API_URL'),
'token' => env('SYNERGYCP_API_TOKEN'),
],
'enhance' => [
'url' => env('ENHANCE_API_URL'),
'token' => env('ENHANCE_API_TOKEN'),
],
]; ];

View File

@@ -1,69 +1,405 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Link } from '@inertiajs/vue3' import { Link } from '@inertiajs/vue3'
import { computed } from 'vue'
import AccountLayout from '@/Layouts/AccountLayout.vue' import AccountLayout from '@/Layouts/AccountLayout.vue'
import StatCard from '@/Components/StatCard.vue' import StatCard from '@/Components/StatCard.vue'
import { resolveSubscriptionStatusColor, resolveInvoiceStatusColor, formatPrice } from '@/utils/resolvers'
import type { Subscription, Invoice } from '@/types'
interface Props { interface Props {
servicesCount: number
activeServicesCount: number activeServicesCount: number
activeSubscriptionsCount: number
activeSubscriptions: Subscription[]
latestInvoices: Invoice[]
pendingInvoicesAmount: string
nextRenewalDate: string | null
} }
defineOptions({ layout: AccountLayout }) defineOptions({ layout: AccountLayout })
defineProps<Props>() const props = defineProps<Props>()
function formatDate(dateString: string): string {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
}
function formatCurrency(amount: string | number): string {
return `$${parseFloat(String(amount)).toFixed(2)}`
}
const formattedNextRenewal = computed<string>(() => {
if (!props.nextRenewalDate) {
return 'N/A'
}
return formatDate(props.nextRenewalDate)
})
const unpaidInvoices = computed<Invoice[]>(() => {
return props.latestInvoices.filter(
(invoice: Invoice) => invoice.status === 'pending' || invoice.status === 'overdue',
)
})
</script> </script>
<template> <template>
<div> <div>
<div class="text-h4 font-weight-bold mb-6">Dashboard</div> <div class="text-h4 font-weight-bold mb-6">
Dashboard
</div>
<VRow> <!-- Row 1: Stats Cards -->
<VCol cols="12" md="4"> <VRow class="mb-2">
<VCol
cols="12"
sm="6"
md="3"
>
<StatCard <StatCard
title="Total Services" title="Active Services"
:stats="servicesCount" :stats="activeServicesCount"
icon="tabler-server" icon="tabler-server"
color="primary" color="primary"
/> />
</VCol> </VCol>
<VCol cols="12" md="4"> <VCol
cols="12"
sm="6"
md="3"
>
<StatCard <StatCard
title="Active Services" title="Subscriptions"
:stats="activeServicesCount" :stats="activeSubscriptionsCount"
icon="tabler-circle-check" icon="tabler-receipt"
color="success" color="success"
/> />
</VCol> </VCol>
<VCol cols="12" md="4"> <VCol
cols="12"
sm="6"
md="3"
>
<StatCard
title="Pending Invoices"
:stats="formatCurrency(pendingInvoicesAmount)"
icon="tabler-file-invoice"
color="warning"
/>
</VCol>
<VCol
cols="12"
sm="6"
md="3"
>
<StatCard
title="Next Renewal"
:stats="formattedNextRenewal"
icon="tabler-calendar-event"
color="info"
/>
</VCol>
</VRow>
<!-- Row 2: Active Subscriptions + Recent Invoices -->
<VRow class="mb-2">
<!-- Active Subscriptions -->
<VCol
cols="12"
md="6"
>
<VCard> <VCard>
<VCardText> <VCardTitle class="d-flex align-center justify-space-between">
<div class="d-flex align-center gap-4"> <span>Active Subscriptions</span>
<VAvatar color="info" variant="tonal" rounded size="44"> <Link
<VIcon icon="tabler-bolt" size="26" /> href="/subscriptions"
</VAvatar> class="text-primary text-body-2 text-decoration-none"
<div> >
<div class="text-body-2 text-medium-emphasis">Quick Actions</div> View All
</Link>
</VCardTitle>
<VCardText v-if="activeSubscriptions.length === 0">
<div class="text-center py-8">
<VIcon
icon="tabler-receipt-off"
size="48"
class="text-disabled mb-3"
/>
<div class="text-body-2 text-medium-emphasis mb-3">
No active subscriptions
</div>
<Link
href="/plans"
class="text-decoration-none"
>
<VBtn
size="small"
color="primary"
>
Browse Plans
</VBtn>
</Link>
</div>
</VCardText>
<VList
v-else
lines="three"
>
<template
v-for="(subscription, index) in activeSubscriptions"
:key="subscription.id"
>
<VListItem>
<template #prepend>
<VAvatar
color="primary"
variant="tonal"
rounded
>
<VIcon icon="tabler-package" />
</VAvatar>
</template>
<VListItemTitle class="font-weight-semibold">
{{ subscription.plan?.name || subscription.type }}
</VListItemTitle>
<VListItemSubtitle>
<div class="d-flex align-center ga-2 mt-1">
<VChip
:color="resolveSubscriptionStatusColor(subscription.stripe_status)"
size="x-small"
class="text-capitalize"
>
{{ subscription.stripe_status }}
</VChip>
<span
v-if="subscription.plan"
class="text-body-2"
>
{{ formatPrice(subscription.plan.price, subscription.plan.billing_cycle) }}
</span>
</div>
<div
v-if="subscription.current_period_end"
class="text-body-2 mt-1"
>
Next billing: {{ formatDate(subscription.current_period_end) }}
</div>
<div
v-if="subscription.ends_at"
class="text-error text-body-2 mt-1"
>
Cancels on {{ formatDate(subscription.ends_at) }}
</div>
</VListItemSubtitle>
<template #append>
<Link
:href="`/subscriptions/${subscription.id}`"
class="text-decoration-none"
>
<VBtn
variant="text"
size="small"
color="primary"
>
Manage
</VBtn>
</Link>
</template>
</VListItem>
<VDivider
v-if="index < activeSubscriptions.length - 1"
inset
/>
</template>
</VList>
</VCard>
</VCol>
<!-- Recent Invoices -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardTitle class="d-flex align-center justify-space-between">
<span>Recent Invoices</span>
<Link
href="/billing/invoices"
class="text-primary text-body-2 text-decoration-none"
>
View All
</Link>
</VCardTitle>
<VCardText v-if="latestInvoices.length === 0">
<div class="text-center py-8">
<VIcon
icon="tabler-file-off"
size="48"
class="text-disabled mb-3"
/>
<div class="text-body-2 text-medium-emphasis">
No invoices yet
</div> </div>
</div> </div>
<div class="mt-4 d-flex flex-column ga-2"> </VCardText>
<Link href="/plans" class="text-primary text-body-2 text-decoration-none d-flex align-center ga-1">
<VIcon icon="tabler-chevron-right" size="16" /> <VTable
Browse Plans v-else
density="comfortable"
>
<thead>
<tr>
<th>Invoice #</th>
<th>Date</th>
<th>Status</th>
<th class="text-end">
Amount
</th>
<th class="text-end" />
</tr>
</thead>
<tbody>
<tr
v-for="invoice in latestInvoices"
:key="invoice.id"
>
<td class="text-body-2">
{{ invoice.number }}
</td>
<td class="text-body-2">
{{ formatDate(invoice.created_at) }}
</td>
<td>
<VChip
:color="resolveInvoiceStatusColor(invoice.status)"
size="small"
class="text-capitalize"
>
{{ invoice.status }}
</VChip>
</td>
<td class="text-end text-body-2">
{{ formatCurrency(invoice.total) }}
</td>
<td class="text-end">
<Link
v-if="invoice.status === 'pending' || invoice.status === 'overdue'"
:href="`/checkout/${invoice.id}`"
class="text-decoration-none"
>
<VBtn
variant="text"
size="small"
color="primary"
>
Pay
</VBtn>
</Link>
<a
v-else-if="invoice.status === 'paid'"
:href="`/billing/invoices/${invoice.id}/download`"
class="text-primary text-decoration-none text-body-2"
>
Download
</a>
</td>
</tr>
</tbody>
</VTable>
</VCard>
</VCol>
</VRow>
<!-- Row 3: Quick Actions -->
<VRow>
<VCol cols="12">
<VCard>
<VCardTitle>Quick Actions</VCardTitle>
<VCardText>
<div class="d-flex flex-wrap ga-3">
<Link
href="/plans"
class="text-decoration-none"
>
<VBtn
color="primary"
variant="tonal"
>
<VIcon
icon="tabler-search"
start
/>
Browse Plans
</VBtn>
</Link> </Link>
<Link href="/subscriptions" class="text-primary text-body-2 text-decoration-none d-flex align-center ga-1">
<VIcon icon="tabler-chevron-right" size="16" /> <Link
My Subscriptions href="/billing/invoices"
class="text-decoration-none"
>
<VBtn
color="info"
variant="tonal"
>
<VIcon
icon="tabler-file-invoice"
start
/>
View All Invoices
</VBtn>
</Link> </Link>
<Link href="/billing" class="text-primary text-body-2 text-decoration-none d-flex align-center ga-1">
<VIcon icon="tabler-chevron-right" size="16" /> <Link
Billing &amp; Payments href="/billing"
</Link> class="text-decoration-none"
<Link href="/profile" class="text-primary text-body-2 text-decoration-none d-flex align-center ga-1"> >
<VIcon icon="tabler-chevron-right" size="16" /> <VBtn
Edit Profile color="success"
variant="tonal"
>
<VIcon
icon="tabler-credit-card"
start
/>
Manage Payment Methods
</VBtn>
</Link> </Link>
<a
href="https://ezscale.support"
target="_blank"
rel="noopener noreferrer"
class="text-decoration-none"
>
<VBtn
color="warning"
variant="tonal"
>
<VIcon
icon="tabler-headset"
start
/>
Get Support
<VIcon
icon="tabler-external-link"
end
size="14"
/>
</VBtn>
</a>
</div> </div>
</VCardText> </VCardText>
</VCard> </VCard>

View File

@@ -0,0 +1,316 @@
<script lang="ts" setup>
import { Link } from '@inertiajs/vue3'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
defineOptions({ layout: MarketingLayout })
const effectiveDate = 'February 1, 2026'
const companyName = 'EZSCALE LLC'
interface Section {
id: string
title: string
}
const sections: Section[] = [
{ id: 'overview', title: '1. Overview' },
{ id: 'prohibited', title: '2. Prohibited Activities' },
{ id: 'network', title: '3. Network Abuse' },
{ id: 'resources', title: '4. Resource Limits and Fair Usage' },
{ id: 'content', title: '5. Content Standards' },
{ id: 'security', title: '6. Security' },
{ id: 'enforcement', title: '7. Enforcement' },
{ id: 'reporting', title: '8. Reporting Abuse' },
{ id: 'changes', title: '9. Changes to This Policy' },
{ id: 'contact', title: '10. Contact Information' },
]
</script>
<template>
<div>
<VContainer class="py-16">
<!-- Header -->
<VRow justify="center">
<VCol cols="12" md="10" lg="8">
<div class="text-center mb-12">
<h1 class="text-h2 font-weight-bold mb-3">Acceptable Use Policy</h1>
<p class="text-body-1 text-medium-emphasis">
Effective Date: {{ effectiveDate }}
</p>
</div>
<VCard variant="outlined">
<VCardText class="pa-6 pa-md-10">
<!-- Introduction -->
<p class="text-body-1 mb-6">
This Acceptable Use Policy ("AUP") governs the use of all services provided by {{ companyName }}
("EZSCALE," "we," "us," or "our"). This AUP is incorporated into and forms part of our
<Link :href="route('terms')" class="text-primary text-decoration-none font-weight-medium">Terms of Service</Link>.
By using our Services, you agree to comply with this policy. Violations may result in suspension
or termination of your account.
</p>
<!-- Table of Contents -->
<VCard variant="tonal" color="primary" class="mb-8">
<VCardText>
<h2 class="text-h6 font-weight-bold mb-3">Table of Contents</h2>
<div class="d-flex flex-column ga-1">
<a
v-for="section in sections"
:key="section.id"
:href="`#${section.id}`"
class="text-body-2 text-primary text-decoration-none"
>
{{ section.title }}
</a>
</div>
</VCardText>
</VCard>
<!-- 1. Overview -->
<div id="overview" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">1. Overview</h2>
<p class="text-body-1 mb-3">
EZSCALE provides hosting infrastructure to a wide range of customers. To ensure the security,
reliability, and performance of our network for all users, we require that all customers use
our Services responsibly and lawfully.
</p>
<p class="text-body-1">
This AUP applies to all users of our Services, including customers, their employees, contractors,
and any third parties who access our Services through a customer's account. Customers are
responsible for ensuring that all users of their account comply with this policy.
</p>
</div>
<!-- 2. Prohibited Activities -->
<div id="prohibited" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">2. Prohibited Activities</h2>
<p class="text-body-1 mb-3">
The following activities are strictly prohibited on our network and infrastructure:
</p>
<h3 class="text-subtitle-1 font-weight-bold mb-2">a) Illegal Content and Activities</h3>
<ul class="text-body-1 mb-4 ml-6">
<li class="mb-1">Hosting, storing, or distributing any content that violates local, state, national, or international law.</li>
<li class="mb-1">Child sexual abuse material (CSAM) in any form. Such content will be immediately reported to the National Center for Missing and Exploited Children (NCMEC) and law enforcement.</li>
<li class="mb-1">Facilitating or promoting illegal activities, including fraud, identity theft, or trafficking.</li>
<li class="mb-1">Hosting content that infringes on intellectual property rights (copyright, trademark, patent) without authorization.</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">b) Spam and Unsolicited Communications</h3>
<ul class="text-body-1 mb-4 ml-6">
<li class="mb-1">Sending unsolicited bulk email (spam), including commercial, political, or religious messages.</li>
<li class="mb-1">Operating open mail relays or open proxy servers.</li>
<li class="mb-1">Sending emails with forged headers or misleading subject lines.</li>
<li class="mb-1">Harvesting email addresses from websites or services without consent.</li>
<li class="mb-1">Violating the CAN-SPAM Act, GDPR, or other anti-spam legislation.</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">c) Malware and Malicious Software</h3>
<ul class="text-body-1 mb-4 ml-6">
<li class="mb-1">Hosting, distributing, or developing malware, viruses, trojans, ransomware, or other malicious software.</li>
<li class="mb-1">Operating phishing websites or pages designed to steal credentials.</li>
<li class="mb-1">Running command-and-control (C2) servers for botnets.</li>
<li class="mb-1">Hosting exploit kits or tools designed to compromise computer systems.</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">d) DDoS and Network Attacks</h3>
<ul class="text-body-1 mb-4 ml-6">
<li class="mb-1">Launching, facilitating, or participating in distributed denial-of-service (DDoS) attacks.</li>
<li class="mb-1">Operating DDoS-for-hire ("booter" or "stresser") services.</li>
<li class="mb-1">Conducting port scanning, vulnerability scanning, or penetration testing of systems you do not own or have written authorization to test.</li>
<li class="mb-1">IP spoofing or forging network packet headers.</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">e) Cryptocurrency Mining</h3>
<ul class="text-body-1 ml-6">
<li class="mb-1">Running cryptocurrency mining software on shared hosting, VPS, or any service without prior written approval from EZSCALE.</li>
<li class="mb-1">Mining operations on dedicated servers may be permitted with prior approval. Contact us for details.</li>
<li class="mb-1">Unauthorized mining will result in immediate service suspension.</li>
</ul>
</div>
<!-- 3. Network Abuse -->
<div id="network" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">3. Network Abuse</h2>
<p class="text-body-1 mb-3">
The following network activities are prohibited:
</p>
<ul class="text-body-1 ml-6">
<li class="mb-1">Consuming excessive bandwidth that degrades the experience of other customers on shared infrastructure.</li>
<li class="mb-1">Running processes that cause sustained high CPU, memory, or I/O load on shared resources beyond your plan's allocation.</li>
<li class="mb-1">Operating public Tor exit nodes without prior written approval.</li>
<li class="mb-1">Operating open DNS resolvers, open SMTP relays, or other services commonly abused for amplification attacks.</li>
<li class="mb-1">Engaging in any activity that interferes with or disrupts the network, servers, or services of EZSCALE or third parties.</li>
<li class="mb-1">Circumventing or attempting to circumvent bandwidth limits, resource quotas, or other service restrictions.</li>
</ul>
</div>
<!-- 4. Resource Limits and Fair Usage -->
<div id="resources" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">4. Resource Limits and Fair Usage</h2>
<p class="text-body-1 mb-3">
While we provide generous resource allocations, all services are subject to fair usage principles:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>Shared Hosting:</strong> Designed for websites and web applications. Not suitable for file storage, backup services, or high-computation workloads.</li>
<li class="mb-1"><strong>VPS Hosting:</strong> Resources are allocated as specified in your plan. Sustained usage at 100% of allocated resources is acceptable, but processes that negatively impact the host node may be throttled.</li>
<li class="mb-1"><strong>Dedicated Servers:</strong> Full use of allocated hardware resources is permitted, subject to the restrictions in this AUP.</li>
<li class="mb-1"><strong>Bandwidth:</strong> Bandwidth quotas are stated in your plan details. Overage charges or throttling may apply when limits are exceeded.</li>
</ul>
<p class="text-body-1">
If your usage significantly exceeds what is typical for your plan, we may contact you to discuss
upgrading to a more appropriate plan. We will always attempt to work with you before taking any
restrictive action.
</p>
</div>
<!-- 5. Content Standards -->
<div id="content" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">5. Content Standards</h2>
<p class="text-body-1 mb-3">
You are responsible for all content hosted on your account. While we generally do not monitor
customer content, we reserve the right to remove or disable access to content that:
</p>
<ul class="text-body-1 ml-6">
<li class="mb-1">Violates any applicable law or regulation.</li>
<li class="mb-1">Infringes on the intellectual property rights of others.</li>
<li class="mb-1">Contains or distributes malware.</li>
<li class="mb-1">Is the subject of a valid DMCA takedown notice or court order.</li>
<li class="mb-1">Poses an immediate risk to our infrastructure, network, or other customers.</li>
</ul>
</div>
<!-- 6. Security -->
<div id="security" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">6. Security</h2>
<p class="text-body-1 mb-3">
Customers are responsible for maintaining the security of their accounts and services:
</p>
<ul class="text-body-1 ml-6">
<li class="mb-1">Keep all software, including operating systems, CMS platforms, and plugins, up to date with security patches.</li>
<li class="mb-1">Use strong, unique passwords and enable two-factor authentication (2FA).</li>
<li class="mb-1">Configure firewalls and access controls appropriately.</li>
<li class="mb-1">Monitor your services for unauthorized access or suspicious activity.</li>
<li class="mb-1">Report security incidents to us promptly at <a href="mailto:abuse@ezscale.cloud" class="text-primary text-decoration-none font-weight-medium">abuse@ezscale.cloud</a>.</li>
</ul>
</div>
<!-- 7. Enforcement -->
<div id="enforcement" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">7. Enforcement</h2>
<p class="text-body-1 mb-3">
We take a graduated approach to enforcement, though we reserve the right to take immediate action
for severe violations:
</p>
<VCard variant="outlined" class="mb-4">
<VTable>
<thead>
<tr>
<th class="text-subtitle-2 font-weight-bold">Step</th>
<th class="text-subtitle-2 font-weight-bold">Action</th>
<th class="text-subtitle-2 font-weight-bold">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<VChip color="warning" size="small" variant="tonal">1</VChip>
</td>
<td class="font-weight-medium">Warning</td>
<td>Written notification of the violation with a request to remedy the issue within a specified timeframe (typically 24-48 hours).</td>
</tr>
<tr>
<td>
<VChip color="error" size="small" variant="tonal">2</VChip>
</td>
<td class="font-weight-medium">Suspension</td>
<td>Temporary suspension of the affected service or account if the violation is not resolved or is repeated. You will be notified and given an opportunity to respond.</td>
</tr>
<tr>
<td>
<VChip color="error" size="small" variant="flat">3</VChip>
</td>
<td class="font-weight-medium">Termination</td>
<td>Permanent termination of the account without refund for severe or repeated violations. Your data will be retained for 30 days before permanent deletion.</td>
</tr>
</tbody>
</VTable>
</VCard>
<VAlert type="warning" variant="tonal" class="mb-0">
<strong>Immediate Action:</strong> For violations that pose an immediate threat to network integrity,
security, or legal compliance (e.g., DDoS attacks, malware distribution, CSAM), we may suspend
services immediately without prior notice.
</VAlert>
</div>
<!-- 8. Reporting Abuse -->
<div id="reporting" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">8. Reporting Abuse</h2>
<p class="text-body-1 mb-3">
If you believe that an EZSCALE customer is violating this Acceptable Use Policy, please report it to us.
We take all abuse reports seriously and will investigate promptly.
</p>
<VCard variant="tonal" color="error" class="mb-3">
<VCardText>
<div class="d-flex align-center ga-3">
<VAvatar color="error" variant="flat" size="48">
<VIcon icon="tabler-alert-triangle" size="24" />
</VAvatar>
<div>
<div class="text-subtitle-1 font-weight-bold">Report Abuse</div>
<a href="mailto:abuse@ezscale.cloud" class="text-body-1 text-decoration-none" style="color: inherit;">abuse@ezscale.cloud</a>
</div>
</div>
</VCardText>
</VCard>
<p class="text-body-1">
When reporting abuse, please include as much detail as possible: IP addresses, timestamps,
log excerpts, email headers, or URLs. This helps us investigate and respond quickly.
</p>
</div>
<!-- 9. Changes to This Policy -->
<div id="changes" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">9. Changes to This Policy</h2>
<p class="text-body-1">
We may update this Acceptable Use Policy from time to time. We will notify customers of material
changes by posting the updated policy on our website and updating the "Effective Date." Continued
use of our Services after changes take effect constitutes acceptance of the revised policy.
</p>
</div>
<!-- 10. Contact Information -->
<div id="contact">
<h2 class="text-h5 font-weight-bold mb-4">10. Contact Information</h2>
<p class="text-body-1 mb-3">
If you have questions about this Acceptable Use Policy, please contact us:
</p>
<VCard variant="tonal" class="mb-0">
<VCardText>
<div class="d-flex flex-column ga-2">
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-building" size="20" class="text-medium-emphasis" />
<span class="text-body-1">{{ companyName }}</span>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-mail" size="20" class="text-medium-emphasis" />
<a href="mailto:abuse@ezscale.cloud" class="text-body-1 text-primary text-decoration-none">abuse@ezscale.cloud</a>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-world" size="20" class="text-medium-emphasis" />
<a href="https://ezscale.cloud" class="text-body-1 text-primary text-decoration-none">ezscale.cloud</a>
</div>
</div>
</VCardText>
</VCard>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
</template>

View File

@@ -0,0 +1,314 @@
<script lang="ts" setup>
import { Link } from '@inertiajs/vue3'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
defineOptions({ layout: MarketingLayout })
const effectiveDate = 'February 1, 2026'
const companyName = 'EZSCALE LLC'
interface Section {
id: string
title: string
}
const sections: Section[] = [
{ id: 'data-collect', title: '1. Data We Collect' },
{ id: 'how-use', title: '2. How We Use Your Data' },
{ id: 'data-sharing', title: '3. Data Sharing and Third Parties' },
{ id: 'data-retention', title: '4. Data Retention' },
{ id: 'security', title: '5. Security Measures' },
{ id: 'rights', title: '6. Your Rights' },
{ id: 'cookies', title: '7. Cookies and Tracking Technologies' },
{ id: 'children', title: '8. Children\'s Privacy' },
{ id: 'international', title: '9. International Data Transfers' },
{ id: 'changes', title: '10. Changes to This Policy' },
{ id: 'contact', title: '11. Contact Information' },
]
</script>
<template>
<div>
<VContainer class="py-16">
<!-- Header -->
<VRow justify="center">
<VCol cols="12" md="10" lg="8">
<div class="text-center mb-12">
<h1 class="text-h2 font-weight-bold mb-3">Privacy Policy</h1>
<p class="text-body-1 text-medium-emphasis">
Effective Date: {{ effectiveDate }}
</p>
</div>
<VCard variant="outlined">
<VCardText class="pa-6 pa-md-10">
<!-- Introduction -->
<p class="text-body-1 mb-6">
{{ companyName }} ("EZSCALE," "we," "us," or "our") is committed to protecting your privacy. This
Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use
our website and services (collectively, the "Services"). By using our Services, you consent to the
practices described in this policy.
</p>
<!-- Table of Contents -->
<VCard variant="tonal" color="primary" class="mb-8">
<VCardText>
<h2 class="text-h6 font-weight-bold mb-3">Table of Contents</h2>
<div class="d-flex flex-column ga-1">
<a
v-for="section in sections"
:key="section.id"
:href="`#${section.id}`"
class="text-body-2 text-primary text-decoration-none"
>
{{ section.title }}
</a>
</div>
</VCardText>
</VCard>
<!-- 1. Data We Collect -->
<div id="data-collect" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">1. Data We Collect</h2>
<p class="text-body-1 mb-3">
We collect different types of information depending on how you interact with our Services:
</p>
<h3 class="text-subtitle-1 font-weight-bold mb-2">a) Account Information</h3>
<ul class="text-body-1 mb-4 ml-6">
<li class="mb-1">Full name and contact information (email address, phone number)</li>
<li class="mb-1">Company name and business information (if applicable)</li>
<li class="mb-1">Username and password (passwords are securely hashed and never stored in plain text)</li>
<li class="mb-1">Mailing address</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">b) Billing and Payment Information</h3>
<ul class="text-body-1 mb-4 ml-6">
<li class="mb-1">Payment method details (credit card numbers are processed and stored by our payment processors, Stripe and PayPal; we do not store full card numbers)</li>
<li class="mb-1">Billing address</li>
<li class="mb-1">Transaction history and invoice records</li>
<li class="mb-1">Tax identification numbers (where required by law)</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">c) Service Usage Data</h3>
<ul class="text-body-1 mb-4 ml-6">
<li class="mb-1">Server resource utilization (CPU, memory, disk, bandwidth)</li>
<li class="mb-1">Service configuration and settings</li>
<li class="mb-1">Support ticket history and communications</li>
<li class="mb-1">API usage logs</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">d) Technical and Log Data</h3>
<ul class="text-body-1 ml-6">
<li class="mb-1">IP addresses and geolocation data</li>
<li class="mb-1">Browser type, operating system, and device information</li>
<li class="mb-1">Pages visited, referral URLs, and session duration</li>
<li class="mb-1">Authentication logs (login timestamps, 2FA events)</li>
</ul>
</div>
<!-- 2. How We Use Your Data -->
<div id="how-use" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">2. How We Use Your Data</h2>
<p class="text-body-1 mb-3">
We use the information we collect for the following purposes:
</p>
<ul class="text-body-1 ml-6">
<li class="mb-1"><strong>Service Delivery:</strong> To provision, manage, and maintain your hosting services and account.</li>
<li class="mb-1"><strong>Billing and Payments:</strong> To process payments, generate invoices, and manage subscriptions.</li>
<li class="mb-1"><strong>Customer Support:</strong> To respond to your inquiries, troubleshoot issues, and provide technical assistance.</li>
<li class="mb-1"><strong>Security:</strong> To detect, prevent, and respond to fraud, abuse, and security threats.</li>
<li class="mb-1"><strong>Communication:</strong> To send transactional emails (invoices, service notifications, password resets) and, with your consent, marketing communications.</li>
<li class="mb-1"><strong>Improvement:</strong> To analyze usage patterns and improve our Services, website, and user experience.</li>
<li class="mb-1"><strong>Legal Compliance:</strong> To comply with applicable laws, regulations, and legal obligations.</li>
</ul>
</div>
<!-- 3. Data Sharing and Third Parties -->
<div id="data-sharing" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">3. Data Sharing and Third Parties</h2>
<p class="text-body-1 mb-3">
We do not sell your personal information. We may share your data with the following categories of third parties:
</p>
<h3 class="text-subtitle-1 font-weight-bold mb-2">a) Payment Processors</h3>
<p class="text-body-1 mb-4">
We use <strong>Stripe</strong> and <strong>PayPal</strong> to process payments. These processors receive
the payment information necessary to complete transactions and are bound by their own privacy policies
and PCI DSS compliance standards.
</p>
<h3 class="text-subtitle-1 font-weight-bold mb-2">b) Infrastructure Partners</h3>
<p class="text-body-1 mb-4">
We partner with data center providers and infrastructure vendors to deliver our Services. These
partners may have limited access to technical data necessary for service provisioning and maintenance.
</p>
<h3 class="text-subtitle-1 font-weight-bold mb-2">c) Legal and Compliance</h3>
<p class="text-body-1 mb-4">
We may disclose your information if required by law, regulation, legal process, or governmental
request, or if we believe disclosure is necessary to protect our rights, your safety, or the
safety of others.
</p>
<h3 class="text-subtitle-1 font-weight-bold mb-2">d) Business Transfers</h3>
<p class="text-body-1">
In the event of a merger, acquisition, or sale of assets, your information may be transferred as
part of the transaction. We will notify you of any such change and any choices you may have.
</p>
</div>
<!-- 4. Data Retention -->
<div id="data-retention" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">4. Data Retention</h2>
<p class="text-body-1 mb-3">
We retain your data for as long as necessary to fulfill the purposes described in this policy:
</p>
<ul class="text-body-1 ml-6">
<li class="mb-1"><strong>Account Data:</strong> Retained for the duration of your account and for 30 days after account closure.</li>
<li class="mb-1"><strong>Billing Records:</strong> Retained for 7 years to comply with tax and financial reporting requirements.</li>
<li class="mb-1"><strong>Server Data:</strong> Your hosted data is deleted within 30 days of service termination.</li>
<li class="mb-1"><strong>Log Data:</strong> Authentication and access logs are retained for 12 months.</li>
<li class="mb-1"><strong>Support Tickets:</strong> Retained for 3 years after resolution for quality assurance and reference.</li>
</ul>
</div>
<!-- 5. Security Measures -->
<div id="security" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">5. Security Measures</h2>
<p class="text-body-1 mb-3">
We implement industry-standard security measures to protect your data, including:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>Encryption:</strong> All data in transit is encrypted using TLS 1.2 or higher. Sensitive data at rest is encrypted using AES-256.</li>
<li class="mb-1"><strong>Access Controls:</strong> Role-based access control (RBAC) limits employee access to customer data on a need-to-know basis.</li>
<li class="mb-1"><strong>Two-Factor Authentication:</strong> Available for all customer accounts and required for all administrative access.</li>
<li class="mb-1"><strong>Network Security:</strong> Firewalls, intrusion detection systems, and DDoS mitigation protect our infrastructure.</li>
<li class="mb-1"><strong>Regular Audits:</strong> Periodic security assessments and vulnerability testing of our systems.</li>
</ul>
<p class="text-body-1">
While we strive to protect your data, no method of transmission or storage is 100% secure. We
cannot guarantee absolute security.
</p>
</div>
<!-- 6. Your Rights -->
<div id="rights" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">6. Your Rights</h2>
<p class="text-body-1 mb-3">
Depending on your jurisdiction, you may have the following rights regarding your personal data:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>Access:</strong> Request a copy of the personal data we hold about you.</li>
<li class="mb-1"><strong>Correction:</strong> Request correction of inaccurate or incomplete data.</li>
<li class="mb-1"><strong>Deletion:</strong> Request deletion of your personal data, subject to legal retention requirements.</li>
<li class="mb-1"><strong>Portability:</strong> Request your data in a structured, machine-readable format.</li>
<li class="mb-1"><strong>Objection:</strong> Object to certain processing activities, including direct marketing.</li>
<li class="mb-1"><strong>Restriction:</strong> Request that we limit the processing of your data in certain circumstances.</li>
<li class="mb-1"><strong>Withdraw Consent:</strong> Where processing is based on consent, you may withdraw it at any time.</li>
</ul>
<h3 class="text-subtitle-1 font-weight-bold mb-2">For EU/EEA Residents (GDPR)</h3>
<p class="text-body-1 mb-3">
If you are located in the European Union or European Economic Area, you have additional rights under
the General Data Protection Regulation (GDPR). We process your data based on legitimate interests
(service delivery, security), contractual necessity, and your consent. You may lodge a complaint
with your local data protection authority.
</p>
<h3 class="text-subtitle-1 font-weight-bold mb-2">For California Residents (CCPA)</h3>
<p class="text-body-1">
California residents have the right to know what personal information we collect, request deletion,
and opt out of the sale of personal information. We do not sell personal information.
</p>
<p class="text-body-1 mt-3">
To exercise any of these rights, contact us at
<a href="mailto:privacy@ezscale.cloud" class="text-primary text-decoration-none font-weight-medium">privacy@ezscale.cloud</a>.
We will respond within 30 days.
</p>
</div>
<!-- 7. Cookies -->
<div id="cookies" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">7. Cookies and Tracking Technologies</h2>
<p class="text-body-1 mb-3">
We use cookies and similar technologies to enhance your experience:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>Essential Cookies:</strong> Required for the website to function properly (authentication, session management, CSRF protection).</li>
<li class="mb-1"><strong>Analytics Cookies:</strong> Help us understand how visitors use our website to improve our services.</li>
<li class="mb-1"><strong>Preference Cookies:</strong> Remember your settings and preferences (language, theme).</li>
</ul>
<p class="text-body-1">
You can manage cookie preferences through your browser settings. Disabling essential cookies may
affect the functionality of our Services.
</p>
</div>
<!-- 8. Children's Privacy -->
<div id="children" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">8. Children's Privacy</h2>
<p class="text-body-1">
Our Services are not intended for individuals under the age of 18. We do not knowingly collect
personal information from children. If we become aware that we have collected data from a child,
we will take steps to delete such information promptly. If you believe a child has provided us
with personal information, please contact us at
<a href="mailto:privacy@ezscale.cloud" class="text-primary text-decoration-none font-weight-medium">privacy@ezscale.cloud</a>.
</p>
</div>
<!-- 9. International Data Transfers -->
<div id="international" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">9. International Data Transfers</h2>
<p class="text-body-1">
Your data may be transferred to and processed in countries other than your own. We ensure that
appropriate safeguards are in place for international transfers, including standard contractual
clauses approved by relevant regulatory authorities. Our primary data processing facilities
are located in the United States.
</p>
</div>
<!-- 10. Changes to This Policy -->
<div id="changes" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">10. Changes to This Policy</h2>
<p class="text-body-1">
We may update this Privacy Policy from time to time. We will notify you of material changes by
posting the updated policy on our website, updating the "Effective Date," and, for significant
changes, sending a notification to the email address associated with your account. Your continued
use of the Services after changes take effect constitutes acceptance of the revised policy.
</p>
</div>
<!-- 11. Contact Information -->
<div id="contact">
<h2 class="text-h5 font-weight-bold mb-4">11. Contact Information</h2>
<p class="text-body-1 mb-3">
If you have any questions about this Privacy Policy or wish to exercise your data rights, please contact us:
</p>
<VCard variant="tonal" class="mb-0">
<VCardText>
<div class="d-flex flex-column ga-2">
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-building" size="20" class="text-medium-emphasis" />
<span class="text-body-1">{{ companyName }}</span>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-mail" size="20" class="text-medium-emphasis" />
<a href="mailto:privacy@ezscale.cloud" class="text-body-1 text-primary text-decoration-none">privacy@ezscale.cloud</a>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-world" size="20" class="text-medium-emphasis" />
<a href="https://ezscale.cloud" class="text-body-1 text-primary text-decoration-none">ezscale.cloud</a>
</div>
</div>
</VCardText>
</VCard>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
</template>

View File

@@ -0,0 +1,380 @@
<script lang="ts" setup>
import { Link } from '@inertiajs/vue3'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
defineOptions({ layout: MarketingLayout })
const effectiveDate = 'February 1, 2026'
const companyName = 'EZSCALE LLC'
interface Section {
id: string
title: string
}
const sections: Section[] = [
{ id: 'overview', title: '1. Overview' },
{ id: 'uptime', title: '2. Uptime Guarantees' },
{ id: 'credits', title: '3. Service Credit Calculation' },
{ id: 'exclusions', title: '4. Exclusions' },
{ id: 'monitoring', title: '5. Monitoring and Reporting' },
{ id: 'claiming', title: '6. How to Claim SLA Credits' },
{ id: 'remedies', title: '7. Remedies' },
{ id: 'changes', title: '8. Changes to This SLA' },
{ id: 'contact', title: '9. Contact Information' },
]
interface UptimeGuarantee {
service: string
uptime: string
monthly: string
color: string
}
const uptimeGuarantees: UptimeGuarantee[] = [
{ service: 'VPS Hosting', uptime: '99.99%', monthly: '4 min 23 sec', color: 'success' },
{ service: 'Dedicated Servers', uptime: '99.99%', monthly: '4 min 23 sec', color: 'success' },
{ service: 'Web Hosting', uptime: '99.9%', monthly: '43 min 50 sec', color: 'primary' },
{ service: 'Game Servers', uptime: '99.9%', monthly: '43 min 50 sec', color: 'primary' },
{ service: 'MySQL Hosting', uptime: '99.9%', monthly: '43 min 50 sec', color: 'primary' },
]
interface CreditTier {
uptime: string
credit: string
}
const creditTiers: CreditTier[] = [
{ uptime: '99.00% - 99.98%', credit: '10x downtime' },
{ uptime: '95.00% - 98.99%', credit: '25x downtime' },
{ uptime: '90.00% - 94.99%', credit: '50x downtime' },
{ uptime: 'Below 90.00%', credit: '100x downtime' },
]
</script>
<template>
<div>
<VContainer class="py-16">
<!-- Header -->
<VRow justify="center">
<VCol cols="12" md="10" lg="8">
<div class="text-center mb-12">
<h1 class="text-h2 font-weight-bold mb-3">Service Level Agreement</h1>
<p class="text-body-1 text-medium-emphasis">
Effective Date: {{ effectiveDate }}
</p>
</div>
<VCard variant="outlined">
<VCardText class="pa-6 pa-md-10">
<!-- Introduction -->
<p class="text-body-1 mb-6">
This Service Level Agreement ("SLA") is part of the
<Link :href="route('terms')" class="text-primary text-decoration-none font-weight-medium">Terms of Service</Link>
between you and {{ companyName }} ("EZSCALE," "we," "us," or "our"). This SLA describes our
uptime commitments and the remedies available to you if we fail to meet them.
</p>
<!-- Table of Contents -->
<VCard variant="tonal" color="primary" class="mb-8">
<VCardText>
<h2 class="text-h6 font-weight-bold mb-3">Table of Contents</h2>
<div class="d-flex flex-column ga-1">
<a
v-for="section in sections"
:key="section.id"
:href="`#${section.id}`"
class="text-body-2 text-primary text-decoration-none"
>
{{ section.title }}
</a>
</div>
</VCardText>
</VCard>
<!-- 1. Overview -->
<div id="overview" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">1. Overview</h2>
<p class="text-body-1 mb-3">
EZSCALE is committed to providing reliable, high-performance hosting infrastructure. This SLA
defines our uptime guarantees and the service credits we provide when we fail to meet those
commitments.
</p>
<p class="text-body-1">
"Uptime" is defined as the percentage of time during a calendar month that your service is
accessible and operational, as measured by our monitoring systems. "Downtime" is any period
during which your service is inaccessible due to causes within our control.
</p>
</div>
<!-- 2. Uptime Guarantees -->
<div id="uptime" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">2. Uptime Guarantees</h2>
<p class="text-body-1 mb-4">
The following uptime guarantees apply to each service category:
</p>
<VCard variant="outlined" class="mb-4">
<VTable>
<thead>
<tr>
<th class="text-subtitle-2 font-weight-bold">Service</th>
<th class="text-subtitle-2 font-weight-bold">Uptime Guarantee</th>
<th class="text-subtitle-2 font-weight-bold">Max Downtime / Month</th>
</tr>
</thead>
<tbody>
<tr v-for="item in uptimeGuarantees" :key="item.service">
<td class="font-weight-medium">{{ item.service }}</td>
<td>
<VChip :color="item.color" size="small" variant="tonal">
{{ item.uptime }}
</VChip>
</td>
<td class="text-medium-emphasis">{{ item.monthly }}</td>
</tr>
</tbody>
</VTable>
</VCard>
<p class="text-body-1">
Uptime is calculated on a monthly basis using the formula:
</p>
<VCard variant="tonal" class="mt-3">
<VCardText class="text-center">
<code class="text-body-1">
Uptime % = ((Total Minutes in Month - Downtime Minutes) / Total Minutes in Month) x 100
</code>
</VCardText>
</VCard>
</div>
<!-- 3. Service Credit Calculation -->
<div id="credits" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">3. Service Credit Calculation</h2>
<p class="text-body-1 mb-3">
If we fail to meet the uptime guarantee for your service, you are eligible for service credits.
Credits are calculated based on the duration of downtime and applied as a multiplier:
</p>
<VCard variant="outlined" class="mb-4">
<VTable>
<thead>
<tr>
<th class="text-subtitle-2 font-weight-bold">Monthly Uptime</th>
<th class="text-subtitle-2 font-weight-bold">Credit Amount</th>
</tr>
</thead>
<tbody>
<tr v-for="tier in creditTiers" :key="tier.uptime">
<td class="font-weight-medium">{{ tier.uptime }}</td>
<td>
<VChip color="primary" size="small" variant="tonal">
{{ tier.credit }}
</VChip>
</td>
</tr>
</tbody>
</VTable>
</VCard>
<h3 class="text-subtitle-1 font-weight-bold mb-2">Credit Calculation Example</h3>
<VCard variant="tonal" class="mb-3">
<VCardText>
<p class="text-body-2 mb-2">
<strong>Scenario:</strong> Your VPS plan costs $50/month. The service experienced 2 hours of
downtime in a month, resulting in 99.72% uptime.
</p>
<p class="text-body-2 mb-2">
<strong>Credit tier:</strong> 99.00% - 99.98% = 10x downtime credit
</p>
<p class="text-body-2 mb-2">
<strong>Hourly rate:</strong> $50 / 720 hours = $0.069/hour
</p>
<p class="text-body-2 mb-0">
<strong>Credit:</strong> 2 hours x $0.069 x 10 = <strong>$1.39 service credit</strong>
</p>
</VCardText>
</VCard>
<p class="text-body-1">
Service credits are applied to your account balance and can be used toward future invoices.
Credits cannot be converted to cash refunds. The maximum credit for any single month shall
not exceed 100% of your monthly service fee for the affected service.
</p>
</div>
<!-- 4. Exclusions -->
<div id="exclusions" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">4. Exclusions</h2>
<p class="text-body-1 mb-3">
The following situations are excluded from our uptime guarantee and do not qualify for service credits:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-2">
<strong>Scheduled Maintenance:</strong> Planned maintenance windows announced at least 48 hours
in advance via email and/or dashboard notification. We schedule maintenance during low-traffic
hours (typically 2:00 AM - 6:00 AM ET) and aim for zero-downtime maintenance whenever possible.
</li>
<li class="mb-2">
<strong>Force Majeure:</strong> Events beyond our reasonable control, including but not limited
to natural disasters, acts of war, terrorism, pandemics, government actions, power grid failures,
or widespread internet outages.
</li>
<li class="mb-2">
<strong>Customer-Caused Issues:</strong> Downtime resulting from your actions, configurations,
or software, including misconfigured firewalls, resource exhaustion from your applications,
or software bugs in your code.
</li>
<li class="mb-2">
<strong>DDoS Attacks:</strong> Downtime caused by distributed denial-of-service attacks targeting
your services, though we will make reasonable efforts to mitigate such attacks.
</li>
<li class="mb-2">
<strong>Third-Party Services:</strong> Outages of third-party services, DNS providers, or upstream
network providers outside of our direct control.
</li>
<li class="mb-2">
<strong>Account Suspension:</strong> Downtime resulting from suspension of your account due to
policy violations, non-payment, or other breaches of our Terms of Service.
</li>
<li class="mb-2">
<strong>Beta/Preview Services:</strong> Services labeled as beta, preview, or experimental are
not covered by this SLA.
</li>
</ul>
</div>
<!-- 5. Monitoring and Reporting -->
<div id="monitoring" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">5. Monitoring and Reporting</h2>
<p class="text-body-1 mb-3">
We continuously monitor our infrastructure using multiple monitoring systems to detect and
respond to service disruptions:
</p>
<ul class="text-body-1 ml-6">
<li class="mb-1"><strong>Network Monitoring:</strong> Real-time monitoring of network connectivity, latency, and packet loss across all data centers.</li>
<li class="mb-1"><strong>Server Monitoring:</strong> Continuous monitoring of server health, including CPU, memory, disk, and service availability.</li>
<li class="mb-1"><strong>Status Page:</strong> Our public status page provides real-time information about service status, active incidents, and scheduled maintenance.</li>
<li class="mb-1"><strong>Incident Response:</strong> Our operations team is available 24/7 to respond to service disruptions. We aim to acknowledge incidents within 15 minutes and provide updates every 30 minutes during active incidents.</li>
</ul>
</div>
<!-- 6. How to Claim SLA Credits -->
<div id="claiming" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">6. How to Claim SLA Credits</h2>
<p class="text-body-1 mb-3">
To request an SLA credit, follow these steps:
</p>
<VCard variant="outlined" class="mb-4">
<VList lines="three">
<VListItem>
<template #prepend>
<VAvatar color="primary" variant="tonal" size="36">
<span class="text-body-2 font-weight-bold">1</span>
</VAvatar>
</template>
<VListItemTitle class="font-weight-bold">Submit a Support Ticket</VListItemTitle>
<VListItemSubtitle class="text-wrap">
Open a support ticket through your account dashboard or email support@ezscale.cloud
with the subject line "SLA Credit Request."
</VListItemSubtitle>
</VListItem>
<VListItem>
<template #prepend>
<VAvatar color="primary" variant="tonal" size="36">
<span class="text-body-2 font-weight-bold">2</span>
</VAvatar>
</template>
<VListItemTitle class="font-weight-bold">Provide Details</VListItemTitle>
<VListItemSubtitle class="text-wrap">
Include your account ID, affected service(s), date and time of the downtime, duration,
and a description of the impact.
</VListItemSubtitle>
</VListItem>
<VListItem>
<template #prepend>
<VAvatar color="primary" variant="tonal" size="36">
<span class="text-body-2 font-weight-bold">3</span>
</VAvatar>
</template>
<VListItemTitle class="font-weight-bold">Timeframe</VListItemTitle>
<VListItemSubtitle class="text-wrap">
Credit requests must be submitted within 30 days of the incident. Requests submitted
after 30 days will not be eligible for credits.
</VListItemSubtitle>
</VListItem>
<VListItem>
<template #prepend>
<VAvatar color="primary" variant="tonal" size="36">
<span class="text-body-2 font-weight-bold">4</span>
</VAvatar>
</template>
<VListItemTitle class="font-weight-bold">Review and Issuance</VListItemTitle>
<VListItemSubtitle class="text-wrap">
We will review your request within 5 business days. If approved, the credit will be
applied to your next billing cycle.
</VListItemSubtitle>
</VListItem>
</VList>
</VCard>
</div>
<!-- 7. Remedies -->
<div id="remedies" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">7. Remedies</h2>
<p class="text-body-1 mb-3">
Service credits as described in this SLA are your sole and exclusive remedy for any failure
by EZSCALE to meet the uptime guarantees. This SLA does not entitle you to any additional
damages, refunds, or other remedies beyond the service credits described herein.
</p>
<p class="text-body-1">
Nothing in this SLA limits or excludes liability that cannot be limited or excluded under
applicable law.
</p>
</div>
<!-- 8. Changes to This SLA -->
<div id="changes" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">8. Changes to This SLA</h2>
<p class="text-body-1">
We may update this SLA from time to time. We will notify you of material changes by posting
the updated SLA on our website and updating the "Effective Date." Changes will not apply
retroactively. If we reduce our uptime guarantees, we will provide at least 30 days' advance
notice. Your continued use of the Services after changes take effect constitutes acceptance
of the revised SLA.
</p>
</div>
<!-- 9. Contact Information -->
<div id="contact">
<h2 class="text-h5 font-weight-bold mb-4">9. Contact Information</h2>
<p class="text-body-1 mb-3">
If you have questions about this Service Level Agreement or need to submit a credit request, please contact us:
</p>
<VCard variant="tonal" class="mb-0">
<VCardText>
<div class="d-flex flex-column ga-2">
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-building" size="20" class="text-medium-emphasis" />
<span class="text-body-1">{{ companyName }}</span>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-mail" size="20" class="text-medium-emphasis" />
<a href="mailto:support@ezscale.cloud" class="text-body-1 text-primary text-decoration-none">support@ezscale.cloud</a>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-world" size="20" class="text-medium-emphasis" />
<a href="https://ezscale.cloud" class="text-body-1 text-primary text-decoration-none">ezscale.cloud</a>
</div>
</div>
</VCardText>
</VCard>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
</template>

View File

@@ -0,0 +1,332 @@
<script lang="ts" setup>
import { Link } from '@inertiajs/vue3'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
defineOptions({ layout: MarketingLayout })
const effectiveDate = 'February 1, 2026'
const companyName = 'EZSCALE LLC'
interface Section {
id: string
title: string
}
const sections: Section[] = [
{ id: 'acceptance', title: '1. Acceptance of Terms' },
{ id: 'services', title: '2. Service Description' },
{ id: 'account', title: '3. Account Registration and Security' },
{ id: 'payment', title: '4. Payment Terms' },
{ id: 'refund', title: '5. Refund Policy' },
{ id: 'acceptable-use', title: '6. Acceptable Use' },
{ id: 'sla', title: '7. Service Level Agreement' },
{ id: 'ip', title: '8. Intellectual Property' },
{ id: 'termination', title: '9. Termination' },
{ id: 'disclaimer', title: '10. Disclaimer of Warranties' },
{ id: 'liability', title: '11. Limitation of Liability' },
{ id: 'indemnification', title: '12. Indemnification' },
{ id: 'governing-law', title: '13. Governing Law' },
{ id: 'changes', title: '14. Changes to Terms' },
{ id: 'contact', title: '15. Contact Information' },
]
</script>
<template>
<div>
<VContainer class="py-16">
<!-- Header -->
<VRow justify="center">
<VCol cols="12" md="10" lg="8">
<div class="text-center mb-12">
<h1 class="text-h2 font-weight-bold mb-3">Terms of Service</h1>
<p class="text-body-1 text-medium-emphasis">
Effective Date: {{ effectiveDate }}
</p>
</div>
<VCard variant="outlined">
<VCardText class="pa-6 pa-md-10">
<!-- Introduction -->
<p class="text-body-1 mb-6">
Welcome to {{ companyName }} ("EZSCALE," "we," "us," or "our"). These Terms of Service ("Terms")
govern your access to and use of our website, products, and services, including but not limited to
virtual private servers (VPS), dedicated servers, web hosting, and related services (collectively,
the "Services"). By accessing or using our Services, you agree to be bound by these Terms.
</p>
<!-- Table of Contents -->
<VCard variant="tonal" color="primary" class="mb-8">
<VCardText>
<h2 class="text-h6 font-weight-bold mb-3">Table of Contents</h2>
<div class="d-flex flex-column ga-1">
<a
v-for="section in sections"
:key="section.id"
:href="`#${section.id}`"
class="text-body-2 text-primary text-decoration-none"
>
{{ section.title }}
</a>
</div>
</VCardText>
</VCard>
<!-- 1. Acceptance of Terms -->
<div id="acceptance" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">1. Acceptance of Terms</h2>
<p class="text-body-1 mb-3">
By creating an account, placing an order, or otherwise using any of our Services, you acknowledge
that you have read, understood, and agree to be bound by these Terms, as well as our
<Link :href="route('privacy')" class="text-primary text-decoration-none font-weight-medium">Privacy Policy</Link>,
<Link :href="route('aup')" class="text-primary text-decoration-none font-weight-medium">Acceptable Use Policy</Link>, and
<Link :href="route('sla')" class="text-primary text-decoration-none font-weight-medium">Service Level Agreement</Link>.
</p>
<p class="text-body-1 mb-3">
If you are using the Services on behalf of an organization, you represent and warrant that you have
the authority to bind that organization to these Terms.
</p>
<p class="text-body-1">
If you do not agree to these Terms, you must not access or use our Services.
</p>
</div>
<!-- 2. Service Description -->
<div id="services" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">2. Service Description</h2>
<p class="text-body-1 mb-3">
EZSCALE provides cloud infrastructure and hosting services, including:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>Virtual Private Servers (VPS):</strong> Virtualized server environments with dedicated resources, root access, and full administrative control.</li>
<li class="mb-1"><strong>Dedicated Servers:</strong> Bare-metal servers with exclusive hardware resources and full administrative access.</li>
<li class="mb-1"><strong>Web Hosting:</strong> Shared and managed hosting plans with cPanel/control panel access for websites and web applications.</li>
<li class="mb-1"><strong>MySQL Hosting:</strong> Managed MySQL database hosting with automated backups and high availability.</li>
<li class="mb-1"><strong>Game Servers:</strong> Managed game server hosting with one-click deployment and mod support.</li>
</ul>
<p class="text-body-1">
The specific features, resources, and limitations of each service are described on our website and
in the applicable service plan documentation. We reserve the right to modify, update, or discontinue
any Service at any time with reasonable notice.
</p>
</div>
<!-- 3. Account Registration and Security -->
<div id="account" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">3. Account Registration and Security</h2>
<p class="text-body-1 mb-3">
To use our Services, you must create an account. You agree to:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1">Provide accurate, current, and complete information during registration.</li>
<li class="mb-1">Maintain and promptly update your account information.</li>
<li class="mb-1">Keep your password and account credentials secure and confidential.</li>
<li class="mb-1">Enable two-factor authentication (2FA) when available for enhanced security.</li>
<li class="mb-1">Notify us immediately of any unauthorized use of your account.</li>
<li class="mb-1">Accept responsibility for all activities that occur under your account.</li>
</ul>
<p class="text-body-1">
You must be at least 18 years of age to create an account and use our Services. We reserve the right
to suspend or terminate accounts that contain false or misleading information.
</p>
</div>
<!-- 4. Payment Terms -->
<div id="payment" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">4. Payment Terms</h2>
<p class="text-body-1 mb-3">
All Services are provided on a subscription basis. By subscribing to a Service, you agree to the following:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>Billing Cycle:</strong> Services are billed on a monthly, quarterly, semi-annual, or annual basis, depending on the plan selected. Billing begins on the date of purchase.</li>
<li class="mb-1"><strong>Auto-Renewal:</strong> Subscriptions automatically renew at the end of each billing cycle unless cancelled before the renewal date. You will be charged the then-current rate for your plan.</li>
<li class="mb-1"><strong>Payment Methods:</strong> We accept credit cards (Visa, Mastercard, American Express) and PayPal. Payment is processed through our secure payment partners, Stripe and PayPal.</li>
<li class="mb-1"><strong>Taxes:</strong> Prices displayed may not include applicable taxes. You are responsible for any sales tax, VAT, or other taxes imposed by your jurisdiction.</li>
<li class="mb-1"><strong>Failed Payments:</strong> If a payment fails, we will attempt to process the payment again. After multiple failed attempts, your service may be suspended or terminated in accordance with our dunning procedures.</li>
<li class="mb-1"><strong>Price Changes:</strong> We may change our pricing with at least 30 days' notice. Existing subscriptions will be honored at their current rate until the end of the current billing cycle.</li>
</ul>
<p class="text-body-1">
All fees are non-refundable except as expressly set forth in Section 5 (Refund Policy).
</p>
</div>
<!-- 5. Refund Policy -->
<div id="refund" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">5. Refund Policy</h2>
<p class="text-body-1 mb-3">
We want you to be satisfied with our Services. The following refund terms apply:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>VPS Hosting:</strong> 14-day money-back guarantee from the date of initial purchase. Refund requests must be submitted within 14 days of the original order.</li>
<li class="mb-1"><strong>Web Hosting:</strong> 30-day money-back guarantee from the date of initial purchase. Refund requests must be submitted within 30 days of the original order.</li>
<li class="mb-1"><strong>Dedicated Servers:</strong> Due to the custom nature of dedicated server provisioning, refunds are evaluated on a case-by-case basis. A prorated refund may be issued within the first 7 days.</li>
<li class="mb-1"><strong>Domain Registrations:</strong> Domain registrations are non-refundable.</li>
<li class="mb-1"><strong>Add-ons and Extras:</strong> Additional services such as SSL certificates, backups, and IP addresses are non-refundable once provisioned.</li>
</ul>
<p class="text-body-1">
Refunds are issued to the original payment method and may take 5-10 business days to process.
To request a refund, contact us at
<a href="mailto:billing@ezscale.cloud" class="text-primary text-decoration-none font-weight-medium">billing@ezscale.cloud</a>.
</p>
</div>
<!-- 6. Acceptable Use -->
<div id="acceptable-use" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">6. Acceptable Use</h2>
<p class="text-body-1 mb-3">
Your use of the Services is subject to our
<Link :href="route('aup')" class="text-primary text-decoration-none font-weight-medium">Acceptable Use Policy</Link>.
You agree not to use our Services for any purpose that is unlawful or prohibited by these Terms. Prohibited uses include, but are not limited to:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1">Sending unsolicited bulk email (spam) or operating mail relays.</li>
<li class="mb-1">Hosting or distributing illegal content, malware, or phishing sites.</li>
<li class="mb-1">Launching or participating in denial-of-service (DDoS) attacks.</li>
<li class="mb-1">Engaging in any activity that disrupts or interferes with our network or other customers' services.</li>
<li class="mb-1">Mining cryptocurrency without prior written approval.</li>
<li class="mb-1">Violating any applicable local, state, national, or international law.</li>
</ul>
<p class="text-body-1">
Violations may result in immediate suspension or termination of your account without refund.
</p>
</div>
<!-- 7. Service Level Agreement -->
<div id="sla" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">7. Service Level Agreement</h2>
<p class="text-body-1 mb-3">
Our commitment to uptime and service reliability is detailed in our
<Link :href="route('sla')" class="text-primary text-decoration-none font-weight-medium">Service Level Agreement (SLA)</Link>.
The SLA outlines our uptime guarantees, credit policies, and the process for reporting and claiming service disruptions.
</p>
<p class="text-body-1">
The SLA is incorporated into these Terms by reference and forms part of your agreement with us.
</p>
</div>
<!-- 8. Intellectual Property -->
<div id="ip" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">8. Intellectual Property</h2>
<p class="text-body-1 mb-3">
All content, trademarks, logos, and intellectual property associated with the EZSCALE brand and
Services are owned by {{ companyName }}. You may not use, reproduce, or distribute any of our
intellectual property without prior written consent.
</p>
<p class="text-body-1">
You retain all rights to the content, data, and applications you host on our infrastructure.
We do not claim ownership of your content.
</p>
</div>
<!-- 9. Termination -->
<div id="termination" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">9. Termination</h2>
<p class="text-body-1 mb-3">
Either party may terminate services as follows:
</p>
<ul class="text-body-1 mb-3 ml-6">
<li class="mb-1"><strong>By You:</strong> You may cancel your subscription at any time through your account dashboard or by contacting support. Cancellation takes effect at the end of your current billing cycle. No partial refunds are issued for unused time unless covered by our refund policy.</li>
<li class="mb-1"><strong>By Us:</strong> We may suspend or terminate your account immediately if you violate these Terms, our Acceptable Use Policy, or fail to pay outstanding balances. We will make reasonable efforts to notify you before termination except in cases of severe abuse.</li>
</ul>
<p class="text-body-1 mb-3">
Upon termination, your data will be retained for 30 days, after which it will be permanently deleted.
You are responsible for exporting your data prior to account termination.
</p>
</div>
<!-- 10. Disclaimer of Warranties -->
<div id="disclaimer" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">10. Disclaimer of Warranties</h2>
<p class="text-body-1">
THE SERVICES ARE PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, AND NON-INFRINGEMENT. EZSCALE DOES NOT WARRANT THAT THE SERVICES WILL BE UNINTERRUPTED,
ERROR-FREE, OR COMPLETELY SECURE. YOUR USE OF THE SERVICES IS AT YOUR SOLE RISK.
</p>
</div>
<!-- 11. Limitation of Liability -->
<div id="liability" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">11. Limitation of Liability</h2>
<p class="text-body-1 mb-3">
TO THE MAXIMUM EXTENT PERMITTED BY LAW, {{ companyName }} AND ITS OFFICERS, DIRECTORS, EMPLOYEES,
AND AGENTS SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE
DAMAGES, INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, USE, OR GOODWILL, ARISING OUT OF OR
RELATED TO YOUR USE OF THE SERVICES.
</p>
<p class="text-body-1">
OUR TOTAL LIABILITY FOR ANY CLAIMS ARISING UNDER THESE TERMS SHALL NOT EXCEED THE AMOUNT YOU PAID
TO US FOR THE SERVICES DURING THE TWELVE (12) MONTHS PRECEDING THE CLAIM.
</p>
</div>
<!-- 12. Indemnification -->
<div id="indemnification" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">12. Indemnification</h2>
<p class="text-body-1">
You agree to indemnify, defend, and hold harmless {{ companyName }}, its affiliates, officers,
directors, employees, and agents from and against any claims, liabilities, damages, losses, and
expenses (including reasonable attorney's fees) arising out of or related to your use of the
Services, your violation of these Terms, or your violation of any rights of a third party.
</p>
</div>
<!-- 13. Governing Law -->
<div id="governing-law" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">13. Governing Law</h2>
<p class="text-body-1 mb-3">
These Terms shall be governed by and construed in accordance with the laws of the State of Georgia,
United States, without regard to its conflict of law provisions.
</p>
<p class="text-body-1">
Any disputes arising under or in connection with these Terms shall be subject to the exclusive
jurisdiction of the state and federal courts located in the State of Georgia. You consent to the
personal jurisdiction and venue of such courts.
</p>
</div>
<!-- 14. Changes to Terms -->
<div id="changes" class="mb-8">
<h2 class="text-h5 font-weight-bold mb-4">14. Changes to Terms</h2>
<p class="text-body-1 mb-3">
We reserve the right to modify these Terms at any time. We will notify you of material changes by
posting the updated Terms on our website and updating the "Effective Date" at the top of this page.
We may also send notice via email for significant changes.
</p>
<p class="text-body-1">
Your continued use of the Services after any changes constitutes your acceptance of the revised Terms.
If you do not agree to the revised Terms, you must stop using the Services.
</p>
</div>
<!-- 15. Contact Information -->
<div id="contact">
<h2 class="text-h5 font-weight-bold mb-4">15. Contact Information</h2>
<p class="text-body-1 mb-3">
If you have any questions about these Terms of Service, please contact us:
</p>
<VCard variant="tonal" class="mb-0">
<VCardText>
<div class="d-flex flex-column ga-2">
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-building" size="20" class="text-medium-emphasis" />
<span class="text-body-1">{{ companyName }}</span>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-mail" size="20" class="text-medium-emphasis" />
<a href="mailto:legal@ezscale.cloud" class="text-body-1 text-primary text-decoration-none">legal@ezscale.cloud</a>
</div>
<div class="d-flex align-center ga-2">
<VIcon icon="tabler-world" size="20" class="text-medium-emphasis" />
<a href="https://ezscale.cloud" class="text-body-1 text-primary text-decoration-none">ezscale.cloud</a>
</div>
</div>
</VCardText>
</VCard>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
</template>

View File

@@ -25,3 +25,9 @@ Route::get('/pricing', function () {
})->name('pricing'); })->name('pricing');
Route::get('/about', fn () => Inertia::render('Marketing/About'))->name('about'); Route::get('/about', fn () => Inertia::render('Marketing/About'))->name('about');
Route::get('/contact', fn () => Inertia::render('Marketing/Contact'))->name('contact'); Route::get('/contact', fn () => Inertia::render('Marketing/Contact'))->name('contact');
// Legal pages
Route::get('/terms-of-service', fn () => Inertia::render('Marketing/TermsOfService'))->name('terms');
Route::get('/privacy-policy', fn () => Inertia::render('Marketing/PrivacyPolicy'))->name('privacy');
Route::get('/acceptable-use', fn () => Inertia::render('Marketing/AcceptableUse'))->name('aup');
Route::get('/sla', fn () => Inertia::render('Marketing/Sla'))->name('sla');