Initial commit: Omada API TypeScript library
This commit is contained in:
0
.env.example
Normal file
0
.env.example
Normal file
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.env
|
||||
.DS_Store
|
||||
|
||||
46
NOTES.md
Normal file
46
NOTES.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Implementation Notes
|
||||
|
||||
## API Endpoint Compatibility
|
||||
|
||||
This library is built based on the Omada Controller REST API specification. However, the actual API endpoints may vary slightly between Omada Controller versions. The following adjustments may be needed:
|
||||
|
||||
### Authentication Endpoint
|
||||
|
||||
The authentication endpoint used is `/api/v2/login`. If your Omada Controller uses a different endpoint, you may need to adjust the `Authentication.ts` file.
|
||||
|
||||
Common alternatives:
|
||||
- `/api/v2/oauth/token` (OAuth2 token endpoint)
|
||||
- `/api/v1/login` (v1 API)
|
||||
|
||||
### API Path Structure
|
||||
|
||||
The library assumes the API follows REST conventions:
|
||||
- `/sites/{siteId}/devices/{deviceId}`
|
||||
- `/sites/{siteId}/vlans/{vlanId}`
|
||||
- etc.
|
||||
|
||||
If your Omada Controller uses a different path structure, you may need to adjust the service classes accordingly.
|
||||
|
||||
## SSL Certificate Handling
|
||||
|
||||
The library supports self-signed certificates by setting `verifySSL: false`. However, when using native `fetch` in Node.js 18+, SSL certificate handling may work differently than with `node-fetch`.
|
||||
|
||||
If you encounter SSL certificate issues:
|
||||
|
||||
1. **Option 1**: Use `node-fetch` package instead of native fetch
|
||||
2. **Option 2**: Set `NODE_TLS_REJECT_UNAUTHORIZED=0` environment variable (development only)
|
||||
3. **Option 3**: Install proper SSL certificates on the Omada Controller
|
||||
|
||||
## Testing with Actual Omada Controller
|
||||
|
||||
Before using in production, test with your actual Omada Controller to verify:
|
||||
|
||||
1. Authentication endpoint and request format
|
||||
2. API response structure
|
||||
3. Error code meanings
|
||||
4. Required vs optional parameters
|
||||
|
||||
## Contributing Improvements
|
||||
|
||||
If you discover API endpoint differences or need additional features, please update the service classes accordingly.
|
||||
|
||||
188
README.md
Normal file
188
README.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Omada API Library
|
||||
|
||||
TypeScript library for interacting with TP-Link Omada Controller REST API.
|
||||
|
||||
## Features
|
||||
|
||||
- Type-safe API client with full TypeScript support
|
||||
- OAuth2 authentication with automatic token refresh
|
||||
- Support for all Omada devices:
|
||||
- Routers (ER605, etc.)
|
||||
- Switches (SG218R, etc.)
|
||||
- Access Points (EAP)
|
||||
- Complete device management (list, configure, reboot, adopt)
|
||||
- Network configuration (VLANs, DHCP, routing)
|
||||
- Firewall and NAT rule management
|
||||
- Switch port configuration and PoE management
|
||||
- Router WAN/LAN configuration
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
|
||||
```typescript
|
||||
import { OmadaClient } from 'omada-api';
|
||||
|
||||
const client = new OmadaClient({
|
||||
baseUrl: 'https://192.168.11.10:8043',
|
||||
clientId: 'your-api-key',
|
||||
clientSecret: 'your-api-secret',
|
||||
siteId: 'your-site-id', // Optional, will auto-detect
|
||||
verifySSL: false, // Set to true for production
|
||||
});
|
||||
```
|
||||
|
||||
### Device Management
|
||||
|
||||
```typescript
|
||||
import { DevicesService, DeviceType } from 'omada-api';
|
||||
|
||||
const devicesService = new DevicesService(client);
|
||||
|
||||
// List all devices
|
||||
const devices = await devicesService.listDevices();
|
||||
|
||||
// Get routers
|
||||
const routers = await devicesService.getRouters();
|
||||
|
||||
// Get switches
|
||||
const switches = await devicesService.getSwitches();
|
||||
|
||||
// Get device details
|
||||
const device = await devicesService.getDevice('device-id');
|
||||
|
||||
// Reboot a device
|
||||
await devicesService.rebootDevice('device-id');
|
||||
```
|
||||
|
||||
### Network Configuration
|
||||
|
||||
```typescript
|
||||
import { NetworksService } from 'omada-api';
|
||||
|
||||
const networksService = new NetworksService(client);
|
||||
|
||||
// List VLANs
|
||||
const vlans = await networksService.listVLANs();
|
||||
|
||||
// Create a VLAN
|
||||
const vlan = await networksService.createVLAN({
|
||||
name: 'VLAN-100',
|
||||
vlanId: 100,
|
||||
subnet: '10.100.0.0/24',
|
||||
gateway: '10.100.0.1',
|
||||
dhcpEnable: true,
|
||||
dhcpRangeStart: '10.100.0.100',
|
||||
dhcpRangeEnd: '10.100.0.200',
|
||||
dns1: '8.8.8.8',
|
||||
dns2: '1.1.1.1',
|
||||
});
|
||||
```
|
||||
|
||||
### Router Operations
|
||||
|
||||
```typescript
|
||||
import { RouterService } from 'omada-api';
|
||||
|
||||
const routerService = new RouterService(client);
|
||||
|
||||
// Get WAN ports
|
||||
const wanPorts = await routerService.getWANPorts('router-device-id');
|
||||
|
||||
// Configure WAN port
|
||||
await routerService.configureWANPort('router-device-id', 1, {
|
||||
connectionType: 'static',
|
||||
ip: '192.168.1.100',
|
||||
gateway: '192.168.1.1',
|
||||
});
|
||||
```
|
||||
|
||||
### Switch Operations
|
||||
|
||||
```typescript
|
||||
import { SwitchService } from 'omada-api';
|
||||
|
||||
const switchService = new SwitchService(client);
|
||||
|
||||
// Get switch ports
|
||||
const ports = await switchService.getSwitchPorts('switch-device-id');
|
||||
|
||||
// Configure a port
|
||||
await switchService.configurePort('switch-device-id', 1, {
|
||||
enable: true,
|
||||
name: 'Port 1',
|
||||
vlanMode: 'access',
|
||||
nativeVlanId: 100,
|
||||
});
|
||||
|
||||
// Control PoE
|
||||
await switchService.setPoE('switch-device-id', 1, true);
|
||||
```
|
||||
|
||||
### Firewall Rules
|
||||
|
||||
```typescript
|
||||
import { FirewallService } from 'omada-api';
|
||||
|
||||
const firewallService = new FirewallService(client);
|
||||
|
||||
// Create firewall rule
|
||||
await firewallService.createFirewallRule({
|
||||
name: 'Allow SSH',
|
||||
enable: true,
|
||||
action: 'allow',
|
||||
protocol: 'tcp',
|
||||
dstPort: '22',
|
||||
direction: 'in',
|
||||
priority: 100,
|
||||
});
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The library can be configured via environment variables:
|
||||
|
||||
```bash
|
||||
OMADA_CONTROLLER_URL=https://192.168.11.10:8043
|
||||
OMADA_API_KEY=your-api-key
|
||||
OMADA_API_SECRET=your-api-secret
|
||||
OMADA_SITE_ID=your-site-id # Optional
|
||||
OMADA_VERIFY_SSL=false # Set to true for production
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library provides specific error classes for different error scenarios:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
OmadaApiError,
|
||||
OmadaAuthenticationError,
|
||||
OmadaDeviceNotFoundError,
|
||||
OmadaConfigurationError,
|
||||
} from 'omada-api';
|
||||
|
||||
try {
|
||||
await devicesService.getDevice('device-id');
|
||||
} catch (error) {
|
||||
if (error instanceof OmadaDeviceNotFoundError) {
|
||||
console.error('Device not found');
|
||||
} else if (error instanceof OmadaAuthenticationError) {
|
||||
console.error('Authentication failed');
|
||||
} else if (error instanceof OmadaApiError) {
|
||||
console.error('API error:', error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
See the TypeScript type definitions for complete API documentation. All services and types are fully typed.
|
||||
|
||||
34
package.json
Normal file
34
package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "omada-api",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript library for TP-Link Omada Controller REST API",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"keywords": [
|
||||
"omada",
|
||||
"tp-link",
|
||||
"router",
|
||||
"switch",
|
||||
"api",
|
||||
"network"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
139
src/auth/Authentication.ts
Normal file
139
src/auth/Authentication.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* OAuth2 authentication and token management for Omada Controller API
|
||||
*/
|
||||
|
||||
import https from 'https';
|
||||
import { OmadaAuthenticationError, OmadaNetworkError } from '../errors/OmadaErrors.js';
|
||||
|
||||
export interface AuthConfig {
|
||||
baseUrl: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
verifySSL?: boolean;
|
||||
}
|
||||
|
||||
interface TokenCache {
|
||||
token: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages OAuth2 authentication and token lifecycle
|
||||
*/
|
||||
export class Authentication {
|
||||
private config: AuthConfig;
|
||||
private tokenCache: TokenCache | null = null;
|
||||
private httpsAgent: https.Agent;
|
||||
|
||||
constructor(config: AuthConfig) {
|
||||
this.config = {
|
||||
verifySSL: true,
|
||||
...config,
|
||||
};
|
||||
|
||||
// Create HTTPS agent with SSL verification control
|
||||
this.httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: this.config.verifySSL ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a valid access token, refreshing if necessary
|
||||
*/
|
||||
async getAccessToken(): Promise<string> {
|
||||
// Check if we have a valid cached token
|
||||
if (this.tokenCache && this.tokenCache.expiresAt > Date.now() + 60000) {
|
||||
// Token is still valid (with 1 minute buffer)
|
||||
return this.tokenCache.token;
|
||||
}
|
||||
|
||||
// Request new token
|
||||
return this.requestToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a new access token from the Omada Controller
|
||||
*/
|
||||
private async requestToken(): Promise<string> {
|
||||
const url = `${this.config.baseUrl}/api/v2/login`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: this.config.clientId,
|
||||
password: this.config.clientSecret,
|
||||
}),
|
||||
// Note: Native fetch in Node.js 18+ doesn't support agent directly
|
||||
// For SSL certificate handling, ensure verifySSL config is set correctly
|
||||
// @ts-expect-error - agent may not be supported by all fetch implementations
|
||||
agent: this.httpsAgent,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new OmadaAuthenticationError(
|
||||
`Failed to authenticate: ${response.status} ${response.statusText} - ${errorText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json() as {
|
||||
errorCode: number;
|
||||
msg?: string;
|
||||
result?: {
|
||||
token?: string;
|
||||
expiresIn?: number;
|
||||
};
|
||||
token?: string;
|
||||
};
|
||||
|
||||
if (data.errorCode !== 0) {
|
||||
throw new OmadaAuthenticationError(
|
||||
`Authentication failed: ${data.msg || 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
|
||||
// Omada Controller returns token in result.token or directly as token
|
||||
const token = data.result?.token || data.token;
|
||||
|
||||
if (!token || typeof token !== 'string') {
|
||||
throw new OmadaAuthenticationError('No token received from server');
|
||||
}
|
||||
|
||||
// Cache token (default expiration: 1 hour, but we'll refresh after 50 minutes)
|
||||
const expiresIn = data.result?.expiresIn || 3600;
|
||||
this.tokenCache = {
|
||||
token,
|
||||
expiresAt: Date.now() + (expiresIn - 600) * 1000, // Refresh 10 minutes before expiry
|
||||
};
|
||||
|
||||
return token;
|
||||
} catch (error) {
|
||||
if (error instanceof OmadaAuthenticationError) {
|
||||
throw error;
|
||||
}
|
||||
throw new OmadaNetworkError(
|
||||
`Failed to connect to Omada Controller: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error : undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached token (force refresh on next request)
|
||||
*/
|
||||
clearToken(): void {
|
||||
this.tokenCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have a valid cached token
|
||||
*/
|
||||
hasValidToken(): boolean {
|
||||
return this.tokenCache !== null && this.tokenCache.expiresAt > Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
182
src/client/OmadaClient.ts
Normal file
182
src/client/OmadaClient.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Core API client for Omada Controller REST API
|
||||
*/
|
||||
|
||||
import https from 'https';
|
||||
import { Authentication, AuthConfig } from '../auth/Authentication.js';
|
||||
import {
|
||||
OmadaApiError,
|
||||
OmadaNetworkError,
|
||||
OmadaDeviceNotFoundError,
|
||||
OmadaConfigurationError,
|
||||
} from '../errors/OmadaErrors.js';
|
||||
import { ApiResponse, ApiRequestOptions, PaginatedResponse } from '../types/api.js';
|
||||
|
||||
export interface OmadaClientConfig extends AuthConfig {
|
||||
siteId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main client class for interacting with Omada Controller API
|
||||
*/
|
||||
export class OmadaClient {
|
||||
private auth: Authentication;
|
||||
private config: OmadaClientConfig;
|
||||
private httpsAgent: https.Agent;
|
||||
private siteId: string | null = null;
|
||||
|
||||
constructor(config: OmadaClientConfig) {
|
||||
this.config = config;
|
||||
this.auth = new Authentication(config);
|
||||
this.httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: config.verifySSL ?? true,
|
||||
});
|
||||
this.siteId = config.siteId || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current site ID (auto-detect if not set)
|
||||
*/
|
||||
async getSiteId(): Promise<string> {
|
||||
if (this.siteId) {
|
||||
return this.siteId;
|
||||
}
|
||||
|
||||
// Auto-detect site ID by getting the default site
|
||||
const sites = await this.request<Array<{ id: string; name: string }>>('GET', '/sites');
|
||||
if (!sites || sites.length === 0) {
|
||||
throw new OmadaApiError('No sites found in Omada Controller');
|
||||
}
|
||||
|
||||
// Use the first site as default
|
||||
this.siteId = sites[0].id;
|
||||
return this.siteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the site ID explicitly
|
||||
*/
|
||||
setSiteId(siteId: string): void {
|
||||
this.siteId = siteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated API request
|
||||
*/
|
||||
async request<T = any>(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
|
||||
endpoint: string,
|
||||
options: Omit<ApiRequestOptions, 'method'> = {}
|
||||
): Promise<T> {
|
||||
const token = await this.auth.getAccessToken();
|
||||
const url = this.buildUrl(endpoint, options.params);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||
// Note: Native fetch in Node.js 18+ doesn't support agent directly
|
||||
// For SSL certificate handling, ensure verifySSL config is set correctly
|
||||
// @ts-expect-error - agent may not be supported by all fetch implementations
|
||||
agent: this.httpsAgent,
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
let data: ApiResponse<T>;
|
||||
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
throw new OmadaApiError(
|
||||
`Invalid JSON response: ${text.substring(0, 200)}`,
|
||||
response.status
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new OmadaApiError(
|
||||
data.msg || `HTTP ${response.status}: ${response.statusText}`,
|
||||
response.status,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
// Check Omada API error code (0 = success)
|
||||
if (data.errorCode !== 0) {
|
||||
const errorMsg = data.msg || `API error code: ${data.errorCode}`;
|
||||
|
||||
// Handle specific error cases
|
||||
if (data.errorCode === 10001) {
|
||||
// Token expired or invalid
|
||||
this.auth.clearToken();
|
||||
throw new OmadaApiError('Authentication token expired', 401, data);
|
||||
}
|
||||
|
||||
if (data.errorCode === 10002 || data.errorCode === 10003) {
|
||||
throw new OmadaDeviceNotFoundError(endpoint);
|
||||
}
|
||||
|
||||
if (data.errorCode >= 10000 && data.errorCode < 20000) {
|
||||
throw new OmadaConfigurationError(errorMsg, data);
|
||||
}
|
||||
|
||||
throw new OmadaApiError(errorMsg, response.status, data);
|
||||
}
|
||||
|
||||
return data.result as T;
|
||||
} catch (error) {
|
||||
if (error instanceof OmadaApiError || error instanceof OmadaDeviceNotFoundError || error instanceof OmadaConfigurationError) {
|
||||
throw error;
|
||||
}
|
||||
throw new OmadaNetworkError(
|
||||
`Request failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error : undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a paginated API request
|
||||
*/
|
||||
async requestPaginated<T = any>(
|
||||
method: 'GET' | 'POST',
|
||||
endpoint: string,
|
||||
options: Omit<ApiRequestOptions, 'method'> = {}
|
||||
): Promise<PaginatedResponse<T>> {
|
||||
const result = await this.request<PaginatedResponse<T>>(method, endpoint, options);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build full URL with query parameters
|
||||
*/
|
||||
private buildUrl(endpoint: string, params?: Record<string, string | number | boolean>): string {
|
||||
const baseUrl = this.config.baseUrl.replace(/\/$/, '');
|
||||
let url = `${baseUrl}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
|
||||
|
||||
if (params && Object.keys(params).length > 0) {
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
url += `?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication instance (for advanced use cases)
|
||||
*/
|
||||
getAuth(): Authentication {
|
||||
return this.auth;
|
||||
}
|
||||
}
|
||||
|
||||
48
src/errors/OmadaErrors.ts
Normal file
48
src/errors/OmadaErrors.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Custom error classes for Omada API operations
|
||||
*/
|
||||
|
||||
export class OmadaApiError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public statusCode?: number,
|
||||
public response?: any
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'OmadaApiError';
|
||||
Object.setPrototypeOf(this, OmadaApiError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class OmadaAuthenticationError extends OmadaApiError {
|
||||
constructor(message: string = 'Authentication failed', response?: any) {
|
||||
super(message, 401, response);
|
||||
this.name = 'OmadaAuthenticationError';
|
||||
Object.setPrototypeOf(this, OmadaAuthenticationError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class OmadaDeviceNotFoundError extends OmadaApiError {
|
||||
constructor(deviceId: string) {
|
||||
super(`Device not found: ${deviceId}`, 404);
|
||||
this.name = 'OmadaDeviceNotFoundError';
|
||||
Object.setPrototypeOf(this, OmadaDeviceNotFoundError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class OmadaConfigurationError extends OmadaApiError {
|
||||
constructor(message: string, response?: any) {
|
||||
super(`Configuration error: ${message}`, 400, response);
|
||||
this.name = 'OmadaConfigurationError';
|
||||
Object.setPrototypeOf(this, OmadaConfigurationError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class OmadaNetworkError extends OmadaApiError {
|
||||
constructor(message: string, originalError?: Error) {
|
||||
super(`Network error: ${message}`, undefined, originalError);
|
||||
this.name = 'OmadaNetworkError';
|
||||
Object.setPrototypeOf(this, OmadaNetworkError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
32
src/index.ts
Normal file
32
src/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Omada API Library
|
||||
*
|
||||
* TypeScript library for interacting with TP-Link Omada Controller REST API
|
||||
*/
|
||||
|
||||
export { OmadaClient, type OmadaClientConfig } from './client/OmadaClient.js';
|
||||
export { Authentication, type AuthConfig } from './auth/Authentication.js';
|
||||
|
||||
// Error classes
|
||||
export {
|
||||
OmadaApiError,
|
||||
OmadaAuthenticationError,
|
||||
OmadaDeviceNotFoundError,
|
||||
OmadaConfigurationError,
|
||||
OmadaNetworkError,
|
||||
} from './errors/OmadaErrors.js';
|
||||
|
||||
// Type definitions
|
||||
export * from './types/api.js';
|
||||
export * from './types/devices.js';
|
||||
export * from './types/networks.js';
|
||||
export * from './types/sites.js';
|
||||
|
||||
// Services
|
||||
export { SitesService } from './services/SitesService.js';
|
||||
export { DevicesService } from './services/DevicesService.js';
|
||||
export { NetworksService } from './services/NetworksService.js';
|
||||
export { FirewallService } from './services/FirewallService.js';
|
||||
export { SwitchService } from './services/SwitchService.js';
|
||||
export { RouterService } from './services/RouterService.js';
|
||||
|
||||
111
src/services/DevicesService.ts
Normal file
111
src/services/DevicesService.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Device management service for all Omada devices
|
||||
*/
|
||||
|
||||
import { OmadaClient } from '../client/OmadaClient.js';
|
||||
import {
|
||||
Device,
|
||||
DeviceType,
|
||||
DeviceStatus,
|
||||
DeviceStatistics,
|
||||
RouterDevice,
|
||||
SwitchDevice,
|
||||
} from '../types/devices.js';
|
||||
|
||||
export interface DeviceListOptions {
|
||||
siteId?: string;
|
||||
type?: DeviceType;
|
||||
status?: DeviceStatus;
|
||||
}
|
||||
|
||||
export class DevicesService {
|
||||
constructor(private client: OmadaClient) {}
|
||||
|
||||
/**
|
||||
* List all devices
|
||||
*/
|
||||
async listDevices(options: DeviceListOptions = {}): Promise<Device[]> {
|
||||
const siteId = options.siteId || (await this.client.getSiteId());
|
||||
const params: Record<string, string> = { siteId };
|
||||
|
||||
if (options.type) {
|
||||
params.type = options.type;
|
||||
}
|
||||
if (options.status !== undefined) {
|
||||
params.status = String(options.status);
|
||||
}
|
||||
|
||||
return this.client.request<Device[]>('GET', '/devices', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device by ID
|
||||
*/
|
||||
async getDevice(deviceId: string, siteId?: string): Promise<Device> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<Device>('GET', `/sites/${effectiveSiteId}/devices/${deviceId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device statistics
|
||||
*/
|
||||
async getDeviceStatistics(
|
||||
deviceId: string,
|
||||
siteId?: string,
|
||||
startTime?: number,
|
||||
endTime?: number
|
||||
): Promise<DeviceStatistics[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (startTime) params.startTime = String(startTime);
|
||||
if (endTime) params.endTime = String(endTime);
|
||||
|
||||
return this.client.request<DeviceStatistics[]>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/statistics`,
|
||||
{ params }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot a device
|
||||
*/
|
||||
async rebootDevice(deviceId: string, siteId?: string): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request('POST', `/sites/${effectiveSiteId}/devices/${deviceId}/reboot`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adopt a device
|
||||
*/
|
||||
async adoptDevice(deviceId: string, siteId?: string): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request('POST', `/sites/${effectiveSiteId}/devices/${deviceId}/adopt`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unadopt a device
|
||||
*/
|
||||
async unadoptDevice(deviceId: string, siteId?: string): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request('POST', `/sites/${effectiveSiteId}/devices/${deviceId}/unadopt`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get routers (ER605, etc.)
|
||||
*/
|
||||
async getRouters(siteId?: string): Promise<RouterDevice[]> {
|
||||
const devices = await this.listDevices({ siteId, type: DeviceType.ROUTER });
|
||||
return devices as RouterDevice[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get switches (SG218R, etc.)
|
||||
*/
|
||||
async getSwitches(siteId?: string): Promise<SwitchDevice[]> {
|
||||
const devices = await this.listDevices({ siteId, type: DeviceType.SWITCH });
|
||||
return devices as SwitchDevice[];
|
||||
}
|
||||
}
|
||||
|
||||
123
src/services/FirewallService.ts
Normal file
123
src/services/FirewallService.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Firewall and NAT rules management service
|
||||
*/
|
||||
|
||||
import { OmadaClient } from '../client/OmadaClient.js';
|
||||
import { FirewallRule, NATRule } from '../types/networks.js';
|
||||
|
||||
export class FirewallService {
|
||||
constructor(private client: OmadaClient) {}
|
||||
|
||||
/**
|
||||
* List firewall rules
|
||||
*/
|
||||
async listFirewallRules(siteId?: string): Promise<FirewallRule[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<FirewallRule[]>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/firewall/rules`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get firewall rule by ID
|
||||
*/
|
||||
async getFirewallRule(ruleId: string, siteId?: string): Promise<FirewallRule> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<FirewallRule>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/firewall/rules/${ruleId}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a firewall rule
|
||||
*/
|
||||
async createFirewallRule(
|
||||
rule: Omit<FirewallRule, 'id'>,
|
||||
siteId?: string
|
||||
): Promise<FirewallRule> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<FirewallRule>(
|
||||
'POST',
|
||||
`/sites/${effectiveSiteId}/firewall/rules`,
|
||||
{ body: rule }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a firewall rule
|
||||
*/
|
||||
async updateFirewallRule(
|
||||
ruleId: string,
|
||||
rule: Partial<FirewallRule>,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/firewall/rules/${ruleId}`,
|
||||
{ body: rule }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a firewall rule
|
||||
*/
|
||||
async deleteFirewallRule(ruleId: string, siteId?: string): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'DELETE',
|
||||
`/sites/${effectiveSiteId}/firewall/rules/${ruleId}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List NAT rules
|
||||
*/
|
||||
async listNATRules(siteId?: string): Promise<NATRule[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<NATRule[]>('GET', `/sites/${effectiveSiteId}/nat/rules`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get NAT rule by ID
|
||||
*/
|
||||
async getNATRule(ruleId: string, siteId?: string): Promise<NATRule> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<NATRule>('GET', `/sites/${effectiveSiteId}/nat/rules/${ruleId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a NAT rule
|
||||
*/
|
||||
async createNATRule(rule: Omit<NATRule, 'id'>, siteId?: string): Promise<NATRule> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<NATRule>('POST', `/sites/${effectiveSiteId}/nat/rules`, {
|
||||
body: rule,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a NAT rule
|
||||
*/
|
||||
async updateNATRule(
|
||||
ruleId: string,
|
||||
rule: Partial<NATRule>,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request('PUT', `/sites/${effectiveSiteId}/nat/rules/${ruleId}`, {
|
||||
body: rule,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a NAT rule
|
||||
*/
|
||||
async deleteNATRule(ruleId: string, siteId?: string): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request('DELETE', `/sites/${effectiveSiteId}/nat/rules/${ruleId}`);
|
||||
}
|
||||
}
|
||||
|
||||
90
src/services/NetworksService.ts
Normal file
90
src/services/NetworksService.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Network and VLAN configuration service
|
||||
*/
|
||||
|
||||
import { OmadaClient } from '../client/OmadaClient.js';
|
||||
import { VLAN, NetworkProfile, DHCPConfig } from '../types/networks.js';
|
||||
|
||||
export class NetworksService {
|
||||
constructor(private client: OmadaClient) {}
|
||||
|
||||
/**
|
||||
* List all VLANs
|
||||
*/
|
||||
async listVLANs(siteId?: string): Promise<VLAN[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<VLAN[]>('GET', `/sites/${effectiveSiteId}/vlans`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VLAN by ID
|
||||
*/
|
||||
async getVLAN(vlanId: string, siteId?: string): Promise<VLAN> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<VLAN>('GET', `/sites/${effectiveSiteId}/vlans/${vlanId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new VLAN
|
||||
*/
|
||||
async createVLAN(vlan: Omit<VLAN, 'id' | 'siteId'>, siteId?: string): Promise<VLAN> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<VLAN>('POST', `/sites/${effectiveSiteId}/vlans`, {
|
||||
body: vlan,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update VLAN configuration
|
||||
*/
|
||||
async updateVLAN(vlanId: string, vlan: Partial<VLAN>, siteId?: string): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request('PUT', `/sites/${effectiveSiteId}/vlans/${vlanId}`, {
|
||||
body: vlan,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a VLAN
|
||||
*/
|
||||
async deleteVLAN(vlanId: string, siteId?: string): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request('DELETE', `/sites/${effectiveSiteId}/vlans/${vlanId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* List network profiles
|
||||
*/
|
||||
async listNetworkProfiles(siteId?: string): Promise<NetworkProfile[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<NetworkProfile[]>('GET', `/sites/${effectiveSiteId}/network-profiles`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get network profile by ID
|
||||
*/
|
||||
async getNetworkProfile(profileId: string, siteId?: string): Promise<NetworkProfile> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<NetworkProfile>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/network-profiles/${profileId}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure DHCP settings
|
||||
*/
|
||||
async configureDHCP(
|
||||
vlanId: string,
|
||||
dhcpConfig: DHCPConfig,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/vlans/${vlanId}/dhcp`,
|
||||
{ body: dhcpConfig }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
144
src/services/RouterService.ts
Normal file
144
src/services/RouterService.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Router-specific operations (ER605, etc.)
|
||||
*/
|
||||
|
||||
import { OmadaClient } from '../client/OmadaClient.js';
|
||||
import { WANPort, LANPort } from '../types/devices.js';
|
||||
import { RoutingRule, DHCPConfig } from '../types/networks.js';
|
||||
|
||||
export class RouterService {
|
||||
constructor(private client: OmadaClient) {}
|
||||
|
||||
/**
|
||||
* Get router WAN ports
|
||||
*/
|
||||
async getWANPorts(deviceId: string, siteId?: string): Promise<WANPort[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<WANPort[]>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/wan`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure WAN port
|
||||
*/
|
||||
async configureWANPort(
|
||||
deviceId: string,
|
||||
portId: number,
|
||||
config: Partial<WANPort>,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/wan/${portId}`,
|
||||
{ body: config }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get router LAN ports
|
||||
*/
|
||||
async getLANPorts(deviceId: string, siteId?: string): Promise<LANPort[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<LANPort[]>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/lan`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure LAN port
|
||||
*/
|
||||
async configureLANPort(
|
||||
deviceId: string,
|
||||
portId: number,
|
||||
config: Partial<LANPort>,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/lan/${portId}`,
|
||||
{ body: config }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List routing rules
|
||||
*/
|
||||
async listRoutingRules(deviceId: string, siteId?: string): Promise<RoutingRule[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<RoutingRule[]>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/routing`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a routing rule
|
||||
*/
|
||||
async createRoutingRule(
|
||||
deviceId: string,
|
||||
rule: Omit<RoutingRule, 'id'>,
|
||||
siteId?: string
|
||||
): Promise<RoutingRule> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<RoutingRule>(
|
||||
'POST',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/routing`,
|
||||
{ body: rule }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a routing rule
|
||||
*/
|
||||
async updateRoutingRule(
|
||||
deviceId: string,
|
||||
ruleId: string,
|
||||
rule: Partial<RoutingRule>,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/routing/${ruleId}`,
|
||||
{ body: rule }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a routing rule
|
||||
*/
|
||||
async deleteRoutingRule(
|
||||
deviceId: string,
|
||||
ruleId: string,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'DELETE',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/routing/${ruleId}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure DHCP for a VLAN/network
|
||||
*/
|
||||
async configureDHCP(
|
||||
deviceId: string,
|
||||
vlanId: string,
|
||||
dhcpConfig: DHCPConfig,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/vlans/${vlanId}/dhcp`,
|
||||
{ body: dhcpConfig }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
41
src/services/SitesService.ts
Normal file
41
src/services/SitesService.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Site management service
|
||||
*/
|
||||
|
||||
import { OmadaClient } from '../client/OmadaClient.js';
|
||||
import { Site, SiteSettings } from '../types/sites.js';
|
||||
|
||||
export class SitesService {
|
||||
constructor(private client: OmadaClient) {}
|
||||
|
||||
/**
|
||||
* List all sites
|
||||
*/
|
||||
async listSites(): Promise<Site[]> {
|
||||
return this.client.request<Site[]>('GET', '/sites');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site details by ID
|
||||
*/
|
||||
async getSite(siteId: string): Promise<Site> {
|
||||
return this.client.request<Site>('GET', `/sites/${siteId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update site settings
|
||||
*/
|
||||
async updateSite(siteId: string, settings: Partial<SiteSettings>): Promise<void> {
|
||||
await this.client.request('PUT', `/sites/${siteId}`, {
|
||||
body: settings,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current site ID (or default site)
|
||||
*/
|
||||
async getCurrentSiteId(): Promise<string> {
|
||||
return this.client.getSiteId();
|
||||
}
|
||||
}
|
||||
|
||||
96
src/services/SwitchService.ts
Normal file
96
src/services/SwitchService.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Switch-specific operations (SG218R, etc.)
|
||||
*/
|
||||
|
||||
import { OmadaClient } from '../client/OmadaClient.js';
|
||||
import { DevicePort } from '../types/devices.js';
|
||||
import { SwitchPortConfig } from '../types/networks.js';
|
||||
|
||||
export class SwitchService {
|
||||
constructor(private client: OmadaClient) {}
|
||||
|
||||
/**
|
||||
* Get switch ports
|
||||
*/
|
||||
async getSwitchPorts(deviceId: string, siteId?: string): Promise<DevicePort[]> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request<DevicePort[]>(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/ports`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a switch port
|
||||
*/
|
||||
async configurePort(
|
||||
deviceId: string,
|
||||
portId: number,
|
||||
config: SwitchPortConfig,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/ports/${portId}`,
|
||||
{ body: config }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get port statistics
|
||||
*/
|
||||
async getPortStatistics(
|
||||
deviceId: string,
|
||||
portId: number,
|
||||
siteId?: string,
|
||||
startTime?: number,
|
||||
endTime?: number
|
||||
): Promise<any> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (startTime) params.startTime = String(startTime);
|
||||
if (endTime) params.endTime = String(endTime);
|
||||
|
||||
return this.client.request(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/ports/${portId}/statistics`,
|
||||
{ params }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PoE status and power usage
|
||||
*/
|
||||
async getPoEStatus(deviceId: string, siteId?: string): Promise<{
|
||||
poeCapable: boolean;
|
||||
totalPower: number;
|
||||
usedPower: number;
|
||||
ports: Array<{ portId: number; enabled: boolean; power: number }>;
|
||||
}> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
return this.client.request(
|
||||
'GET',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/poe`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable PoE on a port
|
||||
*/
|
||||
async setPoE(
|
||||
deviceId: string,
|
||||
portId: number,
|
||||
enabled: boolean,
|
||||
siteId?: string
|
||||
): Promise<void> {
|
||||
const effectiveSiteId = siteId || (await this.client.getSiteId());
|
||||
await this.client.request(
|
||||
'PUT',
|
||||
`/sites/${effectiveSiteId}/devices/${deviceId}/ports/${portId}/poe`,
|
||||
{ body: { enable: enabled } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
41
src/types/api.ts
Normal file
41
src/types/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Core API request and response types
|
||||
*/
|
||||
|
||||
export interface ApiResponse<T = any> {
|
||||
errorCode: number;
|
||||
msg: string;
|
||||
result?: T;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T = any> {
|
||||
currentPage: number;
|
||||
currentSize: number;
|
||||
totalRows: number;
|
||||
totalPage: number;
|
||||
data: T[];
|
||||
}
|
||||
|
||||
export interface ApiRequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
body?: any;
|
||||
params?: Record<string, string | number | boolean>;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
token: string;
|
||||
tokenType: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface OAuth2ClientCredentials {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
|
||||
98
src/types/devices.ts
Normal file
98
src/types/devices.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Device types for Omada devices (ER605, SG218R, EAP, etc.)
|
||||
*/
|
||||
|
||||
export enum DeviceType {
|
||||
ROUTER = 'router',
|
||||
SWITCH = 'switch',
|
||||
ACCESS_POINT = 'ap',
|
||||
GATEWAY = 'gateway',
|
||||
}
|
||||
|
||||
export enum DeviceStatus {
|
||||
ONLINE = 1,
|
||||
OFFLINE = 0,
|
||||
ADOPTING = 2,
|
||||
UNADOPTED = 3,
|
||||
UNKNOWN = 4,
|
||||
UPGRADING = 5,
|
||||
PROVISIONING = 6,
|
||||
REBOOTING = 7,
|
||||
DISCONNECTED = 8,
|
||||
PENDING = 9,
|
||||
}
|
||||
|
||||
export interface Device {
|
||||
id: string;
|
||||
name: string;
|
||||
type: DeviceType;
|
||||
model: string;
|
||||
mac: string;
|
||||
ip: string;
|
||||
status: DeviceStatus;
|
||||
version: string;
|
||||
uptime?: number;
|
||||
cpu?: number;
|
||||
mem?: number;
|
||||
cpuUtil?: number;
|
||||
memUtil?: number;
|
||||
ports?: DevicePort[];
|
||||
siteId?: string;
|
||||
}
|
||||
|
||||
export interface DevicePort {
|
||||
id: number;
|
||||
name: string;
|
||||
enable: boolean;
|
||||
speed?: number;
|
||||
duplex?: string;
|
||||
flowControl?: boolean;
|
||||
status?: 'up' | 'down' | 'unknown';
|
||||
vlanId?: number;
|
||||
poeEnable?: boolean;
|
||||
poePower?: number;
|
||||
}
|
||||
|
||||
export interface DeviceStatistics {
|
||||
deviceId: string;
|
||||
timestamp: number;
|
||||
txBytes?: number;
|
||||
rxBytes?: number;
|
||||
txPkts?: number;
|
||||
rxPkts?: number;
|
||||
cpuUtil?: number;
|
||||
memUtil?: number;
|
||||
}
|
||||
|
||||
export interface RouterDevice extends Device {
|
||||
type: DeviceType.ROUTER;
|
||||
wanPorts?: WANPort[];
|
||||
lanPorts?: LANPort[];
|
||||
}
|
||||
|
||||
export interface SwitchDevice extends Device {
|
||||
type: DeviceType.SWITCH;
|
||||
ports: DevicePort[];
|
||||
poeCapable?: boolean;
|
||||
totalPoEPower?: number;
|
||||
usedPoEPower?: number;
|
||||
}
|
||||
|
||||
export interface WANPort {
|
||||
id: number;
|
||||
name: string;
|
||||
ip?: string;
|
||||
gateway?: string;
|
||||
dns?: string[];
|
||||
status: 'up' | 'down' | 'connecting';
|
||||
connectionType: 'dhcp' | 'static' | 'pppoe';
|
||||
}
|
||||
|
||||
export interface LANPort {
|
||||
id: number;
|
||||
name: string;
|
||||
ip?: string;
|
||||
subnet?: string;
|
||||
enable: boolean;
|
||||
}
|
||||
|
||||
92
src/types/networks.ts
Normal file
92
src/types/networks.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Network and VLAN configuration types
|
||||
*/
|
||||
|
||||
export interface VLAN {
|
||||
id: string;
|
||||
name: string;
|
||||
vlanId: number;
|
||||
subnet: string;
|
||||
gateway: string;
|
||||
dhcpEnable: boolean;
|
||||
dhcpRangeStart?: string;
|
||||
dhcpRangeEnd?: string;
|
||||
dns1?: string;
|
||||
dns2?: string;
|
||||
siteId?: string;
|
||||
}
|
||||
|
||||
export interface NetworkProfile {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'vlan' | 'bridge' | 'wan';
|
||||
vlanId?: number;
|
||||
subnet?: string;
|
||||
gateway?: string;
|
||||
dhcpEnable: boolean;
|
||||
dhcpRangeStart?: string;
|
||||
dhcpRangeEnd?: string;
|
||||
dns?: string[];
|
||||
}
|
||||
|
||||
export interface FirewallRule {
|
||||
id: string;
|
||||
name: string;
|
||||
enable: boolean;
|
||||
action: 'allow' | 'deny' | 'reject';
|
||||
protocol: 'tcp' | 'udp' | 'tcp/udp' | 'icmp' | 'all';
|
||||
srcIp?: string;
|
||||
srcPort?: string;
|
||||
dstIp?: string;
|
||||
dstPort?: string;
|
||||
direction: 'in' | 'out' | 'forward';
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface NATRule {
|
||||
id: string;
|
||||
name: string;
|
||||
enable: boolean;
|
||||
protocol: 'tcp' | 'udp' | 'tcp/udp' | 'all';
|
||||
externalIp?: string;
|
||||
externalPort?: string;
|
||||
internalIp: string;
|
||||
internalPort: string;
|
||||
interface?: string;
|
||||
}
|
||||
|
||||
export interface DHCPConfig {
|
||||
enable: boolean;
|
||||
rangeStart: string;
|
||||
rangeEnd: string;
|
||||
leaseTime: number;
|
||||
gateway: string;
|
||||
dns1: string;
|
||||
dns2?: string;
|
||||
domain?: string;
|
||||
}
|
||||
|
||||
export interface RoutingRule {
|
||||
id: string;
|
||||
name: string;
|
||||
enable: boolean;
|
||||
destination: string;
|
||||
gateway: string;
|
||||
interface?: string;
|
||||
metric?: number;
|
||||
}
|
||||
|
||||
export interface SwitchPortConfig {
|
||||
portId: number;
|
||||
enable: boolean;
|
||||
name?: string;
|
||||
speed?: 'auto' | '10M' | '100M' | '1G' | '2.5G' | '5G' | '10G';
|
||||
duplex?: 'auto' | 'half' | 'full';
|
||||
flowControl?: boolean;
|
||||
vlanMode?: 'access' | 'trunk' | 'hybrid';
|
||||
nativeVlanId?: number;
|
||||
allowedVlans?: number[];
|
||||
poeEnable?: boolean;
|
||||
isolation?: boolean;
|
||||
}
|
||||
|
||||
22
src/types/sites.ts
Normal file
22
src/types/sites.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Site management types
|
||||
*/
|
||||
|
||||
export interface Site {
|
||||
id: string;
|
||||
name: string;
|
||||
desc?: string;
|
||||
region?: string;
|
||||
timezone?: string;
|
||||
country?: string;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export interface SiteSettings {
|
||||
siteId: string;
|
||||
name: string;
|
||||
country?: string;
|
||||
timezone?: string;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user