From 80cca18e907304b83788f2a010acfdfe3e9d139c Mon Sep 17 00:00:00 2001 From: ste87 Date: Sat, 14 Mar 2026 14:01:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20Speed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Speed/Convoy.php | 433 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 Speed/Convoy.php diff --git a/Speed/Convoy.php b/Speed/Convoy.php new file mode 100644 index 0000000..ebb7ad9 --- /dev/null +++ b/Speed/Convoy.php @@ -0,0 +1,433 @@ +config('host'), '/') . '/api/application/' . $url; + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . $this->config('api_key'), + 'Accept' => 'application/json', + ])->$method($req_url, $data); + + if (!$response->successful()) { + throw new Exception($response->json()['message']); + } + + return $response->json() ?? []; + } + + /** + * Get all the configuration for the extension + * + * @param array $values + */ + public function getConfig($values = []): array + { + return [ + [ + 'name' => 'host', + 'type' => 'text', + 'label' => 'Hostname', + 'required' => true, + 'validation' => 'url:http,https', + ], + [ + 'name' => 'api_key', + 'type' => 'text', + 'label' => 'API Key', + 'required' => true, + ], + ]; + } + + /** + * Get product config + * + * @param array $values + */ + public function getProductConfig($values = []): array + { + $nodes = $this->request('nodes'); + $options = []; + foreach ($nodes['data'] as $node) { + $options[$node['id']] = $node['name']; + } + + return [ + [ + 'name' => 'cpu', + 'type' => 'text', + 'label' => 'CPU Cores', + 'required' => true, + ], + [ + 'name' => 'ram', + 'type' => 'text', + 'label' => 'RAM (MiB)', + 'required' => true, + ], + [ + 'name' => 'disk', + 'type' => 'text', + 'label' => 'Disk (MiB)', + 'required' => true, + ], + [ + 'name' => 'bandwidth', + 'type' => 'text', + 'label' => 'Bandwidth (MiB)', + 'required' => false, + ], + [ + 'name' => 'rate_limit', + 'type' => 'text', + 'label' => 'Rate Limit (MiB/s)', + 'required' => false, + 'description' => 'Network speed limit in MiB/s. Set to 0 or leave empty for unlimited.', + ], + [ + 'name' => 'snapshot', + 'type' => 'text', + 'label' => 'Amount of snapshots', + 'required' => true, + ], + [ + 'name' => 'backups', + 'type' => 'text', + 'label' => 'Amount of backups', + 'required' => true, + ], + [ + 'name' => 'node', + 'type' => 'select', + 'label' => 'Nodes', + 'required' => true, + 'options' => $options, + ], + [ + 'name' => 'auto_assign_ip', + 'type' => 'checkbox', + 'label' => 'Auto assign IP from pool', + 'required' => false, + ], + [ + 'name' => 'ipv4', + 'type' => 'number', + 'label' => 'Amount of IPv4 addresses', + 'required' => false, + ], + [ + 'name' => 'ipv6', + 'type' => 'number', + 'label' => 'Amount of IPv6 addresses', + 'required' => false, + ], + [ + 'name' => 'start_on_create', + 'type' => 'checkbox', + 'label' => 'Start Server After Completing Installation', + 'required' => false, + ], + ]; + } + + public function getCheckoutConfig(Product $product): array + { + $node = $product->settings()->where('key', 'node')->first()->value; + + $os = $this->request('nodes/' . $node . '/template-groups'); + $options = []; + foreach ($os['data'] as $os) { + foreach ($os['templates'] as $template) { + foreach ($template as $template1) { + $options[$template1['uuid']] = $template1['name']; + } + } + } + + return [ + [ + 'name' => 'os', + 'type' => 'select', + 'label' => 'Operating System', + 'required' => true, + 'options' => $options, + ], + [ + 'name' => 'hostname', + 'type' => 'text', + 'label' => 'Hostname', + 'placeholder' => 'server.example.com', + 'required' => true, + 'validation' => 'required|string|max:40', + ], + ]; + } + + /** + * Check if currenct configuration is valid + */ + public function testConfig(): bool|string + { + try { + $this->request('servers'); + + return true; + } catch (Exception $e) { + return $e->getMessage(); + } + + return true; + } + + // Convoy is reallyy strict (The account password must contain 8 - 50 characters, 1 uppercase, 1 lowercase, 1 number and 1 special character.) + private function createPassword() + { + $password = Str::password(); + while (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#\$%\^&\*]).{8,50}$/', $password)) { + $password = Str::password(); + } + + return $password; + } + + public function getOrCreateUser($user) + { + $users = $this->request('users', data: ['filter[email]' => $user->email]); + + if (count($users['data']) > 0) { + return [ + 'created' => false, + 'id' => $users['data'][0]['id'], + ]; + } + + $password = $this->createPassword(); + $user = $this->request('users', 'post', [ + 'email' => $user->email, + 'name' => $user->name, + 'password' => $password, + 'root_admin' => false, + ]); + + return [ + 'created' => true, + 'id' => $user['data']['id'], + 'password' => $password, + ]; + } + + /** + * Create a server + * + * @param array $settings (product settings) + * @param array $properties (checkout options) + * @return bool + */ + public function createServer(Service $service, $settings, $properties) + { + $node = $properties['node'] ?? $settings['node']; + $os = $properties['os']; + $hostname = $properties['hostname']; + $password = $properties['password'] ?? $this->createPassword(); + $cpu = $properties['cpu'] ?? $settings['cpu']; + $ram = $properties['ram'] ?? $settings['ram']; + $disk = $properties['disk'] ?? $settings['disk']; + $bandwidth = $properties['bandwidth'] ?? $settings['bandwidth']; + $rateLimit = $properties['rate_limit'] ?? $settings['rate_limit']; + $snapshot = $properties['snapshot'] ?? $settings['snapshot']; + $backups = $properties['backups'] ?? $settings['backups']; + $ipv4 = $properties['ipv4'] ?? $settings['ipv4']; + $ipv6 = $properties['ipv6'] ?? $settings['ipv6']; + + $ips = []; + if ($ipv4 > 0) { + $ip = $this->request('nodes/' . $node . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv4', 'per_page' => $ipv4]); + $ips = array_merge($ips, array_column($ip['data'], 'id')); + } + if ($ipv6 > 0) { + $ip = $this->request('nodes/' . $node . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv6', 'per_page' => $ipv6]); + $ips = array_merge($ips, array_column($ip['data'], 'id')); + } + + $user = $this->getOrCreateUser($service->user); + + $data = [ + 'node_id' => (int) $node, + 'user_id' => $user['id'], + 'name' => $hostname . ' ' . $service->user->name, + 'hostname' => $hostname, + 'vmid' => null, + 'limits' => [ + 'cpu' => (int) $cpu, + 'memory' => $ram * 1024 * 1024, + 'disk' => $disk * 1024 * 1024, + 'snapshots' => (int) $snapshot, + 'bandwidth' => (int) $bandwidth == 0 ? null : (int) $bandwidth * 1024 * 1024, + 'backups' => (int) $backups, + 'address_ids' => $ips, + ], + 'account_password' => $password, + 'template_uuid' => $os, + 'should_create_server' => true, + 'start_on_completion' => isset($properties['start_on_create']) ? (bool) $properties['start_on_create'] : (bool) $settings['start_on_create'], + ]; + + // Add rate limit if set + if (isset($rateLimit) && $rateLimit > 0) { + $data['limits']['rate_limit'] = (float) $rateLimit; + } + + $server = $this->request('servers', 'post', $data); + + if (!isset($server['data'])) { + throw new Exception('Failed to create server'); + } + + $service->properties()->updateOrCreate([ + 'key' => 'server_uuid', + ], [ + 'name' => 'Convoy Server UUID', + 'value' => $server['data']['uuid'], + ]); + + return [ + 'user' => $user, + 'password' => $password, + 'server' => $server['data'], + ]; + } + + public function upgradeServer(Service $service, $settings, $properties) + { + if (!isset($properties['server_uuid'])) { + throw new Exception('Server does not exist'); + } + + $currentData = $this->request('servers/' . $properties['server_uuid']); + $rateLimit = $properties['rate_limit'] ?? $settings['rate_limit']; + + $data = [ + 'address_ids' => [], + 'snapshot_limit' => (int) ($properties['snapshot'] ?? $settings['snapshot']), + 'backup_limit' => (int) ($properties['backups'] ?? $settings['backups']), + 'bandwidth_limit' => (int) ($properties['bandwidth'] ?? $settings['bandwidth']) * 1024 * 1024, + 'cpu' => (int) ($properties['cpu'] ?? $settings['cpu']), + 'memory' => (int) ($properties['ram'] ?? $settings['ram']) * 1024 * 1024, + 'disk' => (int) ($properties['disk'] ?? $settings['disk']) * 1024 * 1024, + ]; + + // Add rate limit if set + if (isset($rateLimit) && $rateLimit > 0) { + $data['rate_limit'] = (float) $rateLimit; + } + + $limitIpv4 = (int) ($properties['ipv4'] ?? $settings['ipv4']); + $limitIpv6 = (int) ($properties['ipv6'] ?? $settings['ipv6']); + // Check if IPv4 has increased + if ($limitIpv4 && $limitIpv4 > count($currentData['data']['limits']['addresses']['ipv4'])) { + $ip = $this->request('nodes/' . $currentData['data']['node_id'] . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv4', 'per_page' => $limitIpv4 - count($currentData['data']['limits']['addresses']['ipv4'])]); + $data['address_ids'] = array_merge(array_column($currentData['data']['limits']['addresses']['ipv4'], 'id'), array_column($ip['data'], 'id')); + } else { + $data['address_ids'] = array_column($currentData['data']['limits']['addresses']['ipv4'], 'id'); + } + // Check if IPv6 has increased + if ($limitIpv6 && $limitIpv6 > count($currentData['data']['limits']['addresses']['ipv6'])) { + $ip = $this->request('nodes/' . $currentData['data']['node_id'] . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv6', 'per_page' => $limitIpv6 - count($currentData['data']['limits']['addresses']['ipv6'])]); + $data['address_ids'] = array_merge($data['address_ids'], array_column($ip['data'], 'id')); + } else { + $data['address_ids'] = array_merge($data['address_ids'], array_column($currentData['data']['limits']['addresses']['ipv6'], 'id')); + } + $data['address_ids'] = array_values(array_unique($data['address_ids'])); + + // Update server + $server = $this->request('servers/' . $properties['server_uuid'] . '/settings/build', 'patch', $data); + if (!isset($server['data'])) { + throw new Exception('Failed to update server'); + } + + return [ + 'server' => $server['data'], + ]; + } + + /** + * Suspend a server + * + * @param array $settings (product settings) + * @param array $properties (checkout options) + * @return bool + */ + public function suspendServer(Service $service, $settings, $properties) + { + if (!isset($properties['server_uuid'])) { + throw new Exception('Server does not exist'); + } + + $this->request('servers/' . $properties['server_uuid'] . '/settings/suspend', 'post'); + } + + /** + * Unsuspend a server + * + * @param array $settings (product settings) + * @param array $properties (checkout options) + * @return bool + */ + public function unsuspendServer(Service $service, $settings, $properties) + { + if (!isset($properties['server_uuid'])) { + throw new Exception('Server does not exist'); + } + + $this->request('servers/' . $properties['server_uuid'] . '/settings/unsuspend', 'post'); + } + + /** + * Terminate a server + * + * @param array $settings (product settings) + * @param array $properties (checkout options) + * @return bool + */ + public function terminateServer(Service $service, $settings, $properties) + { + if (!isset($properties['server_uuid'])) { + throw new Exception('Server does not exist'); + } + + $this->request('servers/' . $properties['server_uuid'], 'delete'); + + // Remove the server_uuid property + $service->properties()->where('key', 'server_uuid')->delete(); + } + + public function getActions(Service $service): array + { + return [ + [ + 'type' => 'button', + 'label' => 'Go to Server', + 'function' => 'ssoLink', + ], + ]; + } + + public function ssoLink(Service $service): string + { + $data = $this->request('users/' . $this->getOrCreateUser($service->user)['id'] . '/generate-sso-token', 'post'); + + return rtrim($this->config('host'), '/') . '/authenticate?token=' . $data['data']['token']; + } +}