186 lines
7.8 KiB
PHP
186 lines
7.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Nas;
|
|
|
|
use App\Models\NasMikrotik;
|
|
use Illuminate\Validation\ValidationException;
|
|
use RouterOS\Client;
|
|
use RouterOS\Query;
|
|
use Throwable;
|
|
|
|
class RouterOsApiService
|
|
{
|
|
public function testConnection(NasMikrotik $mikrotik): array
|
|
{
|
|
$startedAt = hrtime(true);
|
|
$client = $this->connect($mikrotik);
|
|
$identity = $client->query('/system/identity/print')->read()[0] ?? [];
|
|
$resource = $client->query('/system/resource/print')->read()[0] ?? [];
|
|
|
|
return [
|
|
'connected' => true,
|
|
'latency_ms' => (int) round((hrtime(true) - $startedAt) / 1_000_000),
|
|
'identity' => $identity['name'] ?? null,
|
|
'version' => $resource['version'] ?? null,
|
|
'uptime' => $resource['uptime'] ?? null,
|
|
'platform' => $resource['platform'] ?? null,
|
|
'board_name' => $resource['board-name'] ?? null,
|
|
];
|
|
}
|
|
|
|
public function createAccount(NasMikrotik $mikrotik, string $serviceType, array $attributes): array
|
|
{
|
|
$client = $this->connect($mikrotik);
|
|
$this->assertUsernameAvailable($client, $serviceType, $attributes['username']);
|
|
$query = new Query($this->path($serviceType, 'add'));
|
|
foreach ($this->accountAttributes($attributes) as $key => $value) {
|
|
$query->equal($key, $value);
|
|
}
|
|
$client->query($query)->read();
|
|
|
|
return $this->findAccount($client, $serviceType, $attributes['username']);
|
|
}
|
|
|
|
public function updateAccount(NasMikrotik $mikrotik, string $serviceType, string $username, array $attributes): array
|
|
{
|
|
$client = $this->connect($mikrotik);
|
|
$account = $this->findAccount($client, $serviceType, $username);
|
|
$query = (new Query($this->path($serviceType, 'set')))->equal('.id', $account['.id']);
|
|
foreach ($this->accountAttributes($attributes) as $key => $value) {
|
|
$query->equal($key, $value);
|
|
}
|
|
$client->query($query)->read();
|
|
|
|
return $this->findAccount($client, $serviceType, $attributes['username'] ?? $username);
|
|
}
|
|
|
|
public function setEnabled(NasMikrotik $mikrotik, string $serviceType, string $username, bool $enabled): array
|
|
{
|
|
return $this->updateAccount($mikrotik, $serviceType, $username, [
|
|
'disabled' => $enabled ? 'no' : 'yes',
|
|
]);
|
|
}
|
|
|
|
public function deleteAccount(NasMikrotik $mikrotik, string $serviceType, string $username): void
|
|
{
|
|
$client = $this->connect($mikrotik);
|
|
$account = $this->findAccount($client, $serviceType, $username);
|
|
$client->query(
|
|
(new Query($this->path($serviceType, 'remove')))->equal('.id', $account['.id'])
|
|
)->read();
|
|
}
|
|
|
|
public function syncProfile(NasMikrotik $mikrotik, string $serviceType, string $name, array $attributes, string $mode = 'update'): array
|
|
{
|
|
$client = $this->connect($mikrotik);
|
|
$base = $serviceType === 'hotspot' ? '/ip/hotspot/user/profile' : '/ppp/profile';
|
|
$rows = $client->query((new Query("{$base}/print"))->where('name', $name))->read();
|
|
$existing = $rows[0] ?? null;
|
|
if ($mode === 'adopt') {
|
|
if (! $existing) throw ValidationException::withMessages(['profile' => "Profile {$name} tidak ditemukan untuk di-adopt."]);
|
|
return $existing;
|
|
}
|
|
if (! $existing) {
|
|
$query = (new Query("{$base}/add"))->equal('name', $name);
|
|
} else {
|
|
$query = (new Query("{$base}/set"))->equal('.id', $existing['.id']);
|
|
}
|
|
foreach (array_filter($attributes, fn ($value) => $value !== null) as $key => $value) $query->equal($key, $value);
|
|
$client->query($query)->read();
|
|
$rows = $client->query((new Query("{$base}/print"))->where('name', $name))->read();
|
|
return $rows[0] ?? [];
|
|
}
|
|
|
|
public function inspectAccount(NasMikrotik $mikrotik, string $serviceType, string $username): array
|
|
{
|
|
$client = $this->connect($mikrotik);
|
|
$account = $this->findAccount($client, $serviceType, $username);
|
|
$activeBase = in_array($serviceType, ['ppp','pppoe'], true) ? '/ppp/active' : '/ip/hotspot/active';
|
|
$active = $client->query((new Query("{$activeBase}/print"))->where('name', $username))->read()[0] ?? null;
|
|
return ['account' => $account, 'active' => $active];
|
|
}
|
|
|
|
public function disconnectAccount(NasMikrotik $mikrotik, string $serviceType, string $username): void
|
|
{
|
|
$client = $this->connect($mikrotik);
|
|
$activeBase = in_array($serviceType, ['ppp','pppoe'], true) ? '/ppp/active' : '/ip/hotspot/active';
|
|
$rows = $client->query((new Query("{$activeBase}/print"))->where('name', $username))->read();
|
|
foreach ($rows as $row) if (! empty($row['.id'])) $client->query((new Query("{$activeBase}/remove"))->equal('.id', $row['.id']))->read();
|
|
}
|
|
|
|
private function connect(NasMikrotik $mikrotik): Client
|
|
{
|
|
if ($mikrotik->connection_type !== 'api') {
|
|
throw ValidationException::withMessages(['mikrotik' => 'Perangkat tidak menggunakan koneksi RouterOS API.']);
|
|
}
|
|
if ($mikrotik->status !== 'active') {
|
|
throw ValidationException::withMessages(['mikrotik' => 'Perangkat Mikrotik sedang nonaktif.']);
|
|
}
|
|
if (! $mikrotik->host || ! $mikrotik->api_username || ! $mikrotik->api_password) {
|
|
throw ValidationException::withMessages(['mikrotik' => 'Host, username API, dan password API wajib lengkap.']);
|
|
}
|
|
|
|
try {
|
|
return new Client([
|
|
'host' => $mikrotik->host,
|
|
'user' => $mikrotik->api_username,
|
|
'pass' => $mikrotik->api_password,
|
|
'port' => (int) ($mikrotik->api_port ?: 8728),
|
|
'timeout' => (int) ($mikrotik->timeout ?: 10),
|
|
'socket_timeout' => (int) ($mikrotik->timeout ?: 10),
|
|
'attempts' => 1,
|
|
'delay' => 0,
|
|
'ssl' => (int) ($mikrotik->api_port ?: 8728) === 8729,
|
|
]);
|
|
} catch (Throwable $exception) {
|
|
throw ValidationException::withMessages([
|
|
'mikrotik' => 'Koneksi RouterOS API gagal: '.$exception->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function findAccount(Client $client, string $serviceType, string $username): array
|
|
{
|
|
$rows = $client->query(
|
|
(new Query($this->path($serviceType, 'print')))->where('name', $username)
|
|
)->read();
|
|
if (empty($rows[0]['.id'])) {
|
|
throw ValidationException::withMessages(['username' => "Akun {$username} tidak ditemukan di Mikrotik."]);
|
|
}
|
|
|
|
return $rows[0];
|
|
}
|
|
|
|
private function assertUsernameAvailable(Client $client, string $serviceType, string $username): void
|
|
{
|
|
$rows = $client->query(
|
|
(new Query($this->path($serviceType, 'print')))->where('name', $username)
|
|
)->read();
|
|
if ($rows) {
|
|
throw ValidationException::withMessages(['username' => "Username {$username} sudah ada di Mikrotik."]);
|
|
}
|
|
}
|
|
|
|
private function accountAttributes(array $attributes): array
|
|
{
|
|
return array_filter([
|
|
'name' => $attributes['username'] ?? null,
|
|
'password' => $attributes['password'] ?? null,
|
|
'profile' => $attributes['profile'] ?? null,
|
|
'comment' => $attributes['comment'] ?? null,
|
|
'disabled' => $attributes['disabled'] ?? null,
|
|
], fn ($value) => $value !== null);
|
|
}
|
|
|
|
private function path(string $serviceType, string $action): string
|
|
{
|
|
$base = match ($serviceType) {
|
|
'ppp', 'pppoe' => '/ppp/secret',
|
|
'hotspot' => '/ip/hotspot/user',
|
|
default => throw ValidationException::withMessages(['service_type' => 'Tipe layanan harus PPPoE atau Hotspot.']),
|
|
};
|
|
|
|
return "{$base}/{$action}";
|
|
}
|
|
}
|