277 lines
6.6 KiB
TypeScript
277 lines
6.6 KiB
TypeScript
|
|
import type { UnifiController } from './classes.unifi-controller.js';
|
||
|
|
import type { INetworkClient } from './interfaces/index.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Represents a connected network client
|
||
|
|
*/
|
||
|
|
export class UnifiClient implements INetworkClient {
|
||
|
|
/** Reference to parent controller */
|
||
|
|
private controller?: UnifiController;
|
||
|
|
/** Site ID for API calls */
|
||
|
|
private siteId?: string;
|
||
|
|
|
||
|
|
// INetworkClient properties
|
||
|
|
public _id: string;
|
||
|
|
public mac: string;
|
||
|
|
public site_id: string;
|
||
|
|
public is_guest?: boolean;
|
||
|
|
public is_wired: boolean;
|
||
|
|
public first_seen?: number;
|
||
|
|
public last_seen?: number;
|
||
|
|
public hostname?: string;
|
||
|
|
public name?: string;
|
||
|
|
public ip?: string;
|
||
|
|
public network_id?: string;
|
||
|
|
public uplink_mac?: string;
|
||
|
|
public ap_name?: string;
|
||
|
|
public essid?: string;
|
||
|
|
public bssid?: string;
|
||
|
|
public channel?: number;
|
||
|
|
public radio_proto?: string;
|
||
|
|
public signal?: number;
|
||
|
|
public tx_rate?: number;
|
||
|
|
public rx_rate?: number;
|
||
|
|
public tx_bytes?: number;
|
||
|
|
public rx_bytes?: number;
|
||
|
|
public tx_packets?: number;
|
||
|
|
public rx_packets?: number;
|
||
|
|
public sw_port?: number;
|
||
|
|
public usergroup_id?: string;
|
||
|
|
public oui?: string;
|
||
|
|
public noted?: boolean;
|
||
|
|
public user_id?: string;
|
||
|
|
public fingerprint_source?: number;
|
||
|
|
public dev_cat?: number;
|
||
|
|
public dev_family?: number;
|
||
|
|
public dev_vendor?: number;
|
||
|
|
public dev_id?: number;
|
||
|
|
public os_name?: number;
|
||
|
|
public satisfaction?: number;
|
||
|
|
public anomalies?: number;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this._id = '';
|
||
|
|
this.mac = '';
|
||
|
|
this.site_id = '';
|
||
|
|
this.is_wired = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a client instance from API response object
|
||
|
|
*/
|
||
|
|
public static createFromApiObject(
|
||
|
|
apiObject: INetworkClient,
|
||
|
|
controller?: UnifiController,
|
||
|
|
siteId?: string
|
||
|
|
): UnifiClient {
|
||
|
|
const client = new UnifiClient();
|
||
|
|
Object.assign(client, apiObject);
|
||
|
|
client.controller = controller;
|
||
|
|
client.siteId = siteId || apiObject.site_id;
|
||
|
|
return client;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the raw API object representation
|
||
|
|
*/
|
||
|
|
public toApiObject(): INetworkClient {
|
||
|
|
return {
|
||
|
|
_id: this._id,
|
||
|
|
mac: this.mac,
|
||
|
|
site_id: this.site_id,
|
||
|
|
is_guest: this.is_guest,
|
||
|
|
is_wired: this.is_wired,
|
||
|
|
first_seen: this.first_seen,
|
||
|
|
last_seen: this.last_seen,
|
||
|
|
hostname: this.hostname,
|
||
|
|
name: this.name,
|
||
|
|
ip: this.ip,
|
||
|
|
network_id: this.network_id,
|
||
|
|
uplink_mac: this.uplink_mac,
|
||
|
|
ap_name: this.ap_name,
|
||
|
|
essid: this.essid,
|
||
|
|
bssid: this.bssid,
|
||
|
|
channel: this.channel,
|
||
|
|
radio_proto: this.radio_proto,
|
||
|
|
signal: this.signal,
|
||
|
|
tx_rate: this.tx_rate,
|
||
|
|
rx_rate: this.rx_rate,
|
||
|
|
tx_bytes: this.tx_bytes,
|
||
|
|
rx_bytes: this.rx_bytes,
|
||
|
|
tx_packets: this.tx_packets,
|
||
|
|
rx_packets: this.rx_packets,
|
||
|
|
sw_port: this.sw_port,
|
||
|
|
usergroup_id: this.usergroup_id,
|
||
|
|
oui: this.oui,
|
||
|
|
noted: this.noted,
|
||
|
|
user_id: this.user_id,
|
||
|
|
fingerprint_source: this.fingerprint_source,
|
||
|
|
dev_cat: this.dev_cat,
|
||
|
|
dev_family: this.dev_family,
|
||
|
|
dev_vendor: this.dev_vendor,
|
||
|
|
dev_id: this.dev_id,
|
||
|
|
os_name: this.os_name,
|
||
|
|
satisfaction: this.satisfaction,
|
||
|
|
anomalies: this.anomalies,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get display name (name, hostname, or MAC)
|
||
|
|
*/
|
||
|
|
public getDisplayName(): string {
|
||
|
|
return this.name || this.hostname || this.mac;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if client is wireless
|
||
|
|
*/
|
||
|
|
public isWireless(): boolean {
|
||
|
|
return !this.is_wired;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if client is a guest
|
||
|
|
*/
|
||
|
|
public isGuest(): boolean {
|
||
|
|
return this.is_guest === true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get connection type string
|
||
|
|
*/
|
||
|
|
public getConnectionType(): string {
|
||
|
|
if (this.is_wired) {
|
||
|
|
return `Wired (Port ${this.sw_port || 'unknown'})`;
|
||
|
|
}
|
||
|
|
return `Wireless (${this.essid || 'unknown'})`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get signal strength description
|
||
|
|
*/
|
||
|
|
public getSignalQuality(): string {
|
||
|
|
if (this.is_wired) return 'N/A';
|
||
|
|
if (!this.signal) return 'Unknown';
|
||
|
|
|
||
|
|
// Signal is typically in dBm, with higher (less negative) being better
|
||
|
|
const signal = this.signal;
|
||
|
|
if (signal >= -50) return 'Excellent';
|
||
|
|
if (signal >= -60) return 'Good';
|
||
|
|
if (signal >= -70) return 'Fair';
|
||
|
|
if (signal >= -80) return 'Poor';
|
||
|
|
return 'Very Poor';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Block this client from the network
|
||
|
|
*/
|
||
|
|
public async block(): Promise<void> {
|
||
|
|
if (!this.controller || !this.siteId) {
|
||
|
|
throw new Error('Cannot block client: no controller reference');
|
||
|
|
}
|
||
|
|
|
||
|
|
await this.controller.request(
|
||
|
|
'POST',
|
||
|
|
`/api/s/${this.siteId}/cmd/stamgr`,
|
||
|
|
{
|
||
|
|
cmd: 'block-sta',
|
||
|
|
mac: this.mac,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Unblock this client
|
||
|
|
*/
|
||
|
|
public async unblock(): Promise<void> {
|
||
|
|
if (!this.controller || !this.siteId) {
|
||
|
|
throw new Error('Cannot unblock client: no controller reference');
|
||
|
|
}
|
||
|
|
|
||
|
|
await this.controller.request(
|
||
|
|
'POST',
|
||
|
|
`/api/s/${this.siteId}/cmd/stamgr`,
|
||
|
|
{
|
||
|
|
cmd: 'unblock-sta',
|
||
|
|
mac: this.mac,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reconnect (kick) this client
|
||
|
|
*/
|
||
|
|
public async reconnect(): Promise<void> {
|
||
|
|
if (!this.controller || !this.siteId) {
|
||
|
|
throw new Error('Cannot reconnect client: no controller reference');
|
||
|
|
}
|
||
|
|
|
||
|
|
await this.controller.request(
|
||
|
|
'POST',
|
||
|
|
`/api/s/${this.siteId}/cmd/stamgr`,
|
||
|
|
{
|
||
|
|
cmd: 'kick-sta',
|
||
|
|
mac: this.mac,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Rename this client
|
||
|
|
*/
|
||
|
|
public async rename(newName: string): Promise<void> {
|
||
|
|
if (!this.controller || !this.siteId) {
|
||
|
|
throw new Error('Cannot rename client: no controller reference');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if user exists (has fixed IP/config)
|
||
|
|
if (this.user_id) {
|
||
|
|
await this.controller.request(
|
||
|
|
'PUT',
|
||
|
|
`/api/s/${this.siteId}/rest/user/${this.user_id}`,
|
||
|
|
{
|
||
|
|
name: newName,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
// Create user entry for this client
|
||
|
|
await this.controller.request(
|
||
|
|
'POST',
|
||
|
|
`/api/s/${this.siteId}/rest/user`,
|
||
|
|
{
|
||
|
|
mac: this.mac,
|
||
|
|
name: newName,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.name = newName;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get data usage (combined TX and RX bytes)
|
||
|
|
*/
|
||
|
|
public getDataUsage(): number {
|
||
|
|
return (this.tx_bytes || 0) + (this.rx_bytes || 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format data usage as human-readable string
|
||
|
|
*/
|
||
|
|
public getDataUsageFormatted(): string {
|
||
|
|
const bytes = this.getDataUsage();
|
||
|
|
|
||
|
|
if (bytes >= 1024 * 1024 * 1024) {
|
||
|
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||
|
|
}
|
||
|
|
if (bytes >= 1024 * 1024) {
|
||
|
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
||
|
|
}
|
||
|
|
if (bytes >= 1024) {
|
||
|
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
||
|
|
}
|
||
|
|
return `${bytes} B`;
|
||
|
|
}
|
||
|
|
}
|