First iterative "fix" with Codex CLI, reintroducing the device details when viewing devices.
All checks were successful
Deploy Frontend / deploy (push) Successful in 26s
All checks were successful
Deploy Frontend / deploy (push) Successful in 26s
This commit is contained in:
@@ -1,20 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import api from '@/lib/axios';
|
||||
import api from '@/lib/axios';
|
||||
import type { AxiosError } from 'axios';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { DetailedDevice } from '@/types/devices';
|
||||
import { disableConsoleInProd } from '@/lib/disableConsole';
|
||||
disableConsoleInProd();
|
||||
|
||||
// Helper function to convert UTC timestamps to local time
|
||||
const convertUtcToLocal = (utcString: string | null): string | null => {
|
||||
if (!utcString) return null;
|
||||
// Add 'Z' to indicate it's UTC, then convert to local time
|
||||
return new Date(utcString + 'Z').toLocaleString();
|
||||
};
|
||||
|
||||
|
||||
import { DetailedDevice } from '@/types/devices';
|
||||
import { disableConsoleInProd } from '@/lib/disableConsole';
|
||||
disableConsoleInProd();
|
||||
|
||||
interface DriveInfo {
|
||||
name: string;
|
||||
@@ -32,13 +24,365 @@ interface MacAddress {
|
||||
interfaceName: string;
|
||||
macAddress: string;
|
||||
}
|
||||
|
||||
interface InstalledApp {
|
||||
app_name: string;
|
||||
app_version: string;
|
||||
publisher: string;
|
||||
}
|
||||
|
||||
|
||||
interface InstalledApp {
|
||||
app_name: string;
|
||||
app_version: string;
|
||||
publisher: string;
|
||||
}
|
||||
|
||||
type RawRecord = Record<string, unknown>;
|
||||
|
||||
const pick = (source: RawRecord | null | undefined, keys: string[]): unknown => {
|
||||
if (!source) return undefined;
|
||||
for (const key of keys) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
return source[key];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const toOptionalString = (value: unknown): string | undefined => {
|
||||
if (value === null || value === undefined) return undefined;
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const toNonEmptyString = (value: unknown, fallback = ''): string => {
|
||||
const candidate = toOptionalString(value)?.trim();
|
||||
return candidate && candidate.length > 0 ? candidate : fallback;
|
||||
};
|
||||
|
||||
const parseSizeToGB = (value: unknown): number => {
|
||||
if (value === null || value === undefined) return 0;
|
||||
|
||||
const normalizeNumeric = (num: number): number => {
|
||||
if (!Number.isFinite(num)) return 0;
|
||||
if (num > 1024 ** 3) return +(num / 1024 ** 3).toFixed(2); // bytes
|
||||
if (num > 1024 ** 2) return +(num / 1024 ** 2).toFixed(2); // KB
|
||||
if (num > 1024) return +(num / 1024).toFixed(2); // MB
|
||||
return +num.toFixed(2); // already GB
|
||||
};
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return normalizeNumeric(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return 0;
|
||||
|
||||
const numeric = parseFloat(trimmed.replace(/[^\d.\-]/g, ''));
|
||||
if (!Number.isFinite(numeric)) return 0;
|
||||
|
||||
if (/tb/i.test(trimmed)) return +(numeric * 1024).toFixed(2);
|
||||
if (/gb/i.test(trimmed)) return +numeric.toFixed(2);
|
||||
if (/mb/i.test(trimmed)) return +(numeric / 1024).toFixed(2);
|
||||
if (/kb/i.test(trimmed)) return +(numeric / (1024 ** 2)).toFixed(2);
|
||||
if (/bytes?/i.test(trimmed)) return +(numeric / (1024 ** 3)).toFixed(2);
|
||||
|
||||
return normalizeNumeric(numeric);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const normalizeDrive = (driveInput: unknown, index: number): DriveInfo | null => {
|
||||
if (!driveInput || typeof driveInput !== 'object') return null;
|
||||
const drive = driveInput as RawRecord;
|
||||
|
||||
const name = toNonEmptyString(
|
||||
pick(drive, ['name', 'driveName', 'label', 'volume']) ?? `Drive ${index + 1}`,
|
||||
`Drive ${index + 1}`
|
||||
);
|
||||
const driveType = toNonEmptyString(pick(drive, ['driveType', 'drive_type', 'type']), 'Unknown');
|
||||
const totalSizeGB = parseSizeToGB(
|
||||
pick(drive, [
|
||||
'totalSizeGB',
|
||||
'total_size_gb',
|
||||
'totalSize',
|
||||
'total_size',
|
||||
'size',
|
||||
'capacity',
|
||||
'totalBytes',
|
||||
'total_bytes',
|
||||
])
|
||||
);
|
||||
const freeSpaceGB = parseSizeToGB(
|
||||
pick(drive, [
|
||||
'freeSpaceGB',
|
||||
'free_space_gb',
|
||||
'freeSpace',
|
||||
'free_space',
|
||||
'free',
|
||||
'freeBytes',
|
||||
'free_bytes',
|
||||
'availableBytes',
|
||||
])
|
||||
);
|
||||
|
||||
if (!name && !totalSizeGB && !freeSpaceGB) return null;
|
||||
|
||||
return {
|
||||
name,
|
||||
driveType,
|
||||
totalSizeGB,
|
||||
freeSpaceGB,
|
||||
};
|
||||
};
|
||||
|
||||
const extractDrives = (rawInput: unknown): DriveInfo[] => {
|
||||
if (!rawInput || typeof rawInput !== 'object') return [];
|
||||
const raw = rawInput as RawRecord;
|
||||
|
||||
const potentialArrays = [
|
||||
pick(raw, ['drives']),
|
||||
pick(raw, ['driveInfo']),
|
||||
pick(raw, ['drive_info']),
|
||||
pick(raw, ['storageDevices']),
|
||||
pick(raw, ['drive_details']),
|
||||
pick(raw, ['storage']),
|
||||
];
|
||||
|
||||
for (const candidate of potentialArrays) {
|
||||
if (Array.isArray(candidate)) {
|
||||
return candidate
|
||||
.map((drive, idx: number) => normalizeDrive(drive, idx))
|
||||
.filter((drive): drive is DriveInfo => Boolean(drive));
|
||||
}
|
||||
}
|
||||
|
||||
const aggregatedDrive = {
|
||||
totalSize: pick(raw, ['total_disk_space', 'totalDiskSpace', 'total_storage']),
|
||||
freeSpace: pick(raw, ['free_disk_space', 'freeDiskSpace', 'available_storage']),
|
||||
};
|
||||
|
||||
if (aggregatedDrive.totalSize || aggregatedDrive.freeSpace) {
|
||||
const normalized = normalizeDrive(
|
||||
{
|
||||
name: 'System',
|
||||
driveType: 'Unknown',
|
||||
totalSizeGB: aggregatedDrive.totalSize,
|
||||
freeSpaceGB: aggregatedDrive.freeSpace,
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
return normalized ? [normalized] : [];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const normalizeIpAddress = (entryInput: unknown, index: number): IpAddress | null => {
|
||||
if (!entryInput || typeof entryInput !== 'object') return null;
|
||||
const entry = entryInput as RawRecord;
|
||||
|
||||
const ipAddress = toNonEmptyString(
|
||||
pick(entry, ['ipAddress', 'ip_address', 'address', 'ip', 'ipv4', 'ipv6']) ?? '',
|
||||
''
|
||||
);
|
||||
|
||||
const interfaceName = toNonEmptyString(
|
||||
pick(entry, ['interfaceName', 'interface_name', 'adapter', 'name', 'nic']) ?? '',
|
||||
ipAddress ? `Interface ${index + 1}` : ''
|
||||
);
|
||||
|
||||
if (!ipAddress && !interfaceName) return null;
|
||||
|
||||
const macAddress = toOptionalString(
|
||||
pick(entry, ['macAddress', 'mac_address', 'mac', 'physicalAddress'])
|
||||
);
|
||||
|
||||
return {
|
||||
interfaceName: interfaceName || `Interface ${index + 1}`,
|
||||
ipAddress: ipAddress || 'Unknown',
|
||||
macAddress: macAddress?.trim() || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const extractIpAddresses = (rawInput: unknown): IpAddress[] => {
|
||||
if (!rawInput || typeof rawInput !== 'object') return [];
|
||||
const raw = rawInput as RawRecord;
|
||||
|
||||
const potentialArrays = [
|
||||
pick(raw, ['ipAddresses']),
|
||||
pick(raw, ['ip_addresses']),
|
||||
pick(raw, ['networkInterfaces']),
|
||||
pick(raw, ['network_interfaces']),
|
||||
pick(raw, ['interfaces']),
|
||||
];
|
||||
|
||||
for (const candidate of potentialArrays) {
|
||||
if (Array.isArray(candidate)) {
|
||||
return candidate
|
||||
.map((entry, idx: number) => normalizeIpAddress(entry, idx))
|
||||
.filter((entry): entry is IpAddress => Boolean(entry));
|
||||
}
|
||||
}
|
||||
|
||||
const singleIp = pick(raw, ['ipAddress', 'ip_address']);
|
||||
if (singleIp) {
|
||||
return [
|
||||
normalizeIpAddress(
|
||||
{
|
||||
ipAddress: singleIp,
|
||||
interfaceName: pick(raw, ['interfaceName', 'interface_name', 'adapter']),
|
||||
macAddress: pick(raw, ['macAddress', 'mac_address']),
|
||||
},
|
||||
0
|
||||
) as IpAddress,
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const normalizeInstalledApp = (appInput: unknown): InstalledApp | null => {
|
||||
if (!appInput || typeof appInput !== 'object') return null;
|
||||
const app = appInput as RawRecord;
|
||||
|
||||
const appName = toNonEmptyString(pick(app, ['app_name', 'name', 'title']) ?? '', '');
|
||||
if (!appName) return null;
|
||||
|
||||
return {
|
||||
app_name: appName,
|
||||
app_version: toNonEmptyString(pick(app, ['app_version', 'version']) ?? '', ''),
|
||||
publisher: toNonEmptyString(pick(app, ['publisher', 'manufacturer', 'vendor']) ?? '', ''),
|
||||
};
|
||||
};
|
||||
|
||||
const extractInstalledApps = (rawInput: unknown): InstalledApp[] => {
|
||||
if (!rawInput || typeof rawInput !== 'object') return [];
|
||||
const raw = rawInput as RawRecord;
|
||||
|
||||
const potentialArrays = [
|
||||
pick(raw, ['installedApplications']),
|
||||
pick(raw, ['installed_applications']),
|
||||
pick(raw, ['software']),
|
||||
pick(raw, ['applications']),
|
||||
pick(raw, ['apps']),
|
||||
];
|
||||
|
||||
for (const candidate of potentialArrays) {
|
||||
if (Array.isArray(candidate)) {
|
||||
return candidate
|
||||
.map((app) => normalizeInstalledApp(app))
|
||||
.filter((app): app is InstalledApp => Boolean(app));
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const extractMacAddresses = (rawInput: unknown, ipAddresses: IpAddress[]): MacAddress[] => {
|
||||
if (!rawInput || typeof rawInput !== 'object') return [];
|
||||
const raw = rawInput as RawRecord;
|
||||
const potentialArrays = pick(raw, ['macAddresses', 'mac_addresses']);
|
||||
|
||||
if (Array.isArray(potentialArrays)) {
|
||||
return potentialArrays
|
||||
.map((entry, idx: number) => {
|
||||
if (!entry || typeof entry !== 'object') return null;
|
||||
const record = entry as RawRecord;
|
||||
const macAddress = toOptionalString(pick(record, ['macAddress', 'mac_address', 'mac']));
|
||||
if (!macAddress) return null;
|
||||
return {
|
||||
interfaceName: toNonEmptyString(
|
||||
pick(record, ['interfaceName', 'interface_name', 'adapter']) ?? `Interface ${idx + 1}`,
|
||||
`Interface ${idx + 1}`
|
||||
),
|
||||
macAddress: macAddress.trim(),
|
||||
};
|
||||
})
|
||||
.filter((entry): entry is MacAddress => Boolean(entry));
|
||||
}
|
||||
|
||||
return ipAddresses
|
||||
.filter((entry) => Boolean(entry.macAddress))
|
||||
.map((entry) => ({
|
||||
interfaceName: entry.interfaceName,
|
||||
macAddress: entry.macAddress as string,
|
||||
}));
|
||||
};
|
||||
|
||||
const splitGpuNames = (value: unknown): string[] => {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((item) => toNonEmptyString(item, ''))
|
||||
.filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
.split(/[,;\n]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const normalizeDevice = (rawInput: unknown): DetailedDevice => {
|
||||
const raw: RawRecord =
|
||||
rawInput && typeof rawInput === 'object' ? (rawInput as RawRecord) : {};
|
||||
|
||||
const deviceId = Number(
|
||||
pick(raw, ['deviceId', 'device_id', 'id', 'deviceID', 'device_id_pk']) ?? 0
|
||||
);
|
||||
|
||||
const drives = extractDrives(raw);
|
||||
const ipAddresses = extractIpAddresses(raw);
|
||||
const macAddresses = extractMacAddresses(raw, ipAddresses);
|
||||
const clientEntry = pick(raw, ['client']);
|
||||
const clientNameFromNested =
|
||||
clientEntry && typeof clientEntry === 'object'
|
||||
? pick(clientEntry as RawRecord, ['name'])
|
||||
: undefined;
|
||||
|
||||
const hostname = toNonEmptyString(
|
||||
pick(raw, ['hostname', 'hostName', 'computerName', 'deviceName']) ?? `Device ${deviceId}`,
|
||||
deviceId ? `Device ${deviceId}` : 'Unknown Device'
|
||||
);
|
||||
|
||||
return {
|
||||
deviceId,
|
||||
hostname,
|
||||
osName: toNonEmptyString(pick(raw, ['osName', 'os_name', 'operatingSystem', 'os']) ?? '', ''),
|
||||
osVersion: toNonEmptyString(pick(raw, ['osVersion', 'os_version', 'osVersionString']) ?? '', ''),
|
||||
windowsVersion: toNonEmptyString(pick(raw, ['windowsVersion', 'windows_version', 'osRelease']) ?? '', ''),
|
||||
windowsBuild: toNonEmptyString(pick(raw, ['windowsBuild', 'windows_build', 'buildNumber']) ?? '', ''),
|
||||
osArchitecture: toNonEmptyString(pick(raw, ['osArchitecture', 'os_architecture', 'architecture']) ?? '', ''),
|
||||
processorName: toNonEmptyString(pick(raw, ['processorName', 'processor_name', 'cpuName']) ?? '', ''),
|
||||
processorArchitecture: toNonEmptyString(
|
||||
pick(raw, ['processorArchitecture', 'processor_architecture', 'cpuArchitecture']) ?? '',
|
||||
''
|
||||
),
|
||||
gpuNames: splitGpuNames(pick(raw, ['gpuNames', 'gpu_names', 'gpu_name', 'gpus'])),
|
||||
totalMemory: toNonEmptyString(
|
||||
pick(raw, ['totalMemory', 'total_memory', 'memory', 'totalMemoryMb', 'totalMemoryMB']) ?? '',
|
||||
''
|
||||
),
|
||||
lastBootTime: toNonEmptyString(
|
||||
pick(raw, ['lastBootTime', 'last_boot_time', 'bootTime', 'lastBoot']) ?? '',
|
||||
''
|
||||
),
|
||||
lastCheckedIn: toNonEmptyString(
|
||||
pick(raw, ['lastCheckedIn', 'last_checked_in', 'lastSeen', 'checked_in_at']) ?? '',
|
||||
''
|
||||
),
|
||||
drives,
|
||||
ipAddresses,
|
||||
macAddresses,
|
||||
installedApplications: extractInstalledApps(raw),
|
||||
clientName:
|
||||
toOptionalString(
|
||||
pick(raw, ['clientName', 'client_name']) ?? clientNameFromNested ?? pick(raw, ['clientIdentifier'])
|
||||
) ?? undefined,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
interface DeviceVulnerability {
|
||||
cveId: string;
|
||||
@@ -49,16 +393,21 @@ interface DeviceVulnerability {
|
||||
lastModifiedDate: string;
|
||||
}
|
||||
|
||||
interface CachedSoftwareEntry {
|
||||
id: number;
|
||||
softwareName: string;
|
||||
hostname: string;
|
||||
version: string;
|
||||
deviceId: number;
|
||||
totalCves: number;
|
||||
lastUpdated: string;
|
||||
}
|
||||
|
||||
interface CachedSoftwareEntry {
|
||||
id: number;
|
||||
softwareName: string;
|
||||
hostname: string;
|
||||
version: string;
|
||||
deviceId: number;
|
||||
totalCves: number;
|
||||
lastUpdated: string;
|
||||
}
|
||||
|
||||
interface RawDevicesResponse {
|
||||
devices?: unknown[];
|
||||
vulnerabilitiesByDevice?: { [deviceId: string]: DeviceVulnerability[] };
|
||||
}
|
||||
|
||||
interface DeviceContextType {
|
||||
devices: DetailedDevice[];
|
||||
deviceVulns: { [deviceId: string]: DeviceVulnerability[] };
|
||||
@@ -93,10 +442,12 @@ export const DeviceProvider = ({
|
||||
vulnerabilitiesByDevice: { [deviceId: string]: DeviceVulnerability[] };
|
||||
};
|
||||
}) => {
|
||||
const [devices, setDevices] = useState<DetailedDevice[]>(() => {
|
||||
if (initialData?.devices?.length) return initialData.devices;
|
||||
return [];
|
||||
});
|
||||
const [devices, setDevices] = useState<DetailedDevice[]>(() => {
|
||||
if (initialData?.devices?.length) {
|
||||
return initialData.devices.map((device) => normalizeDevice(device));
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [deviceVulns, setDeviceVulns] = useState<{ [deviceId: string]: DeviceVulnerability[] }>(
|
||||
initialData?.vulnerabilitiesByDevice ?? {}
|
||||
@@ -123,28 +474,32 @@ export const DeviceProvider = ({
|
||||
|
||||
console.group('📡 Fetching Devices, Vulnerabilities, and Software');
|
||||
|
||||
const [devicesRes, softwareRes] = await Promise.all([
|
||||
api.get(devicesEndpoint, { withCredentials: true }),
|
||||
api.get(softwareEndpoint, { withCredentials: true }),
|
||||
]);
|
||||
const [devicesRes, softwareRes] = await Promise.all([
|
||||
api.get<RawDevicesResponse>(devicesEndpoint, { withCredentials: true }),
|
||||
api.get<CachedSoftwareEntry[]>(softwareEndpoint, { withCredentials: true }),
|
||||
]);
|
||||
|
||||
console.log('✅ Devices fetched:', devicesRes.data);
|
||||
console.log('✅ Software fetched:', softwareRes.data);
|
||||
console.groupEnd();
|
||||
|
||||
// STEP 1: Set basic data (keep original UTC timestamps for calculations)
|
||||
setDevices(devicesRes.data.devices);
|
||||
setDeviceVulns(devicesRes.data.vulnerabilitiesByDevice);
|
||||
setCachedSoftware(softwareRes.data);
|
||||
const normalizedDevices = Array.isArray(devicesRes.data?.devices)
|
||||
? (devicesRes.data.devices as unknown[]).map((device) => normalizeDevice(device))
|
||||
: [];
|
||||
|
||||
setDevices(normalizedDevices);
|
||||
setDeviceVulns(devicesRes.data?.vulnerabilitiesByDevice ?? {});
|
||||
setCachedSoftware(softwareRes.data ?? []);
|
||||
|
||||
// STEP 2: Extract all unique CVE IDs
|
||||
const uniqueCveIds = new Set<string>();
|
||||
(Object.values(devicesRes.data.vulnerabilitiesByDevice) as DeviceVulnerability[][]).forEach((vulns) => {
|
||||
vulns.forEach((vuln) => {
|
||||
if (vuln.cveId) {
|
||||
uniqueCveIds.add(vuln.cveId);
|
||||
}
|
||||
});
|
||||
Object.values(devicesRes.data?.vulnerabilitiesByDevice ?? {}).forEach((vulns) => {
|
||||
vulns.forEach((vuln) => {
|
||||
if (vuln.cveId) {
|
||||
uniqueCveIds.add(vuln.cveId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// STEP 3: Fetch detailed CVE data
|
||||
@@ -171,35 +526,38 @@ const cveDetailsRes = await api.get<DeviceVulnerability[]>('/vuln/cves/lookup',
|
||||
setDetailedCveLookup(lookupMap);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.group('❌ Device fetch failed');
|
||||
console.error('🔴 Axios Error Message:', error.message);
|
||||
console.error('🧾 Axios Error Config:', error.config);
|
||||
if (error.response) {
|
||||
console.error('📉 Status:', error.response.status);
|
||||
console.error('📄 Response Headers:', error.response.headers);
|
||||
console.error('📄 Response Data:', error.response.data);
|
||||
} else if (error.request) {
|
||||
console.error('🛑 No response received:', error.request);
|
||||
}
|
||||
console.groupEnd();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.group('❌ Device fetch failed');
|
||||
const axiosError = error as AxiosError;
|
||||
console.error('🔴 Axios Error Message:', axiosError.message);
|
||||
console.error('🧾 Axios Error Config:', axiosError.config);
|
||||
if (axiosError.response) {
|
||||
console.error('📉 Status:', axiosError.response.status);
|
||||
console.error('📄 Response Headers:', axiosError.response.headers);
|
||||
console.error('📄 Response Data:', axiosError.response.data);
|
||||
} else if (axiosError.request) {
|
||||
console.error('🛑 No response received:', axiosError.request);
|
||||
} else {
|
||||
console.error('❓ Unexpected error:', error);
|
||||
}
|
||||
console.groupEnd();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
fetchDevicesVulnsAndSoftware();
|
||||
}, [devices.length, initialData, username, authLoading]);
|
||||
}, [devices.length, initialData, username, authLoading, roles]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("🧠 Devices updated:", devices);
|
||||
}, [devices]);
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).__debug_devices = devices;
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as Window & { __debug_devices?: DetailedDevice[] }).__debug_devices = devices;
|
||||
}
|
||||
|
||||
return (
|
||||
<DeviceContext.Provider value={{
|
||||
|
||||
@@ -6,10 +6,11 @@ export interface DriveInfo {
|
||||
freeSpaceGB: number;
|
||||
}
|
||||
|
||||
export interface IpAddress {
|
||||
interfaceName: string;
|
||||
ipAddress: string;
|
||||
}
|
||||
export interface IpAddress {
|
||||
interfaceName: string;
|
||||
ipAddress: string;
|
||||
macAddress?: string;
|
||||
}
|
||||
|
||||
export interface MacAddress {
|
||||
interfaceName: string;
|
||||
@@ -42,4 +43,4 @@ export interface DriveInfo {
|
||||
installedApplications: InstalledApp[];
|
||||
clientName?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user