mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-09 13:38:05 +08:00
239 lines
6.1 KiB
JavaScript
239 lines
6.1 KiB
JavaScript
'use strict';
|
|
|
|
'require baseclass';
|
|
|
|
const UtilIpv6 = baseclass.extend({
|
|
/**
|
|
* Derive ULA IPv6 subnet and gateway from IPv4 subnet.
|
|
* Uses IPv4 octets 3-4 as subnet ID for consistent IPv4/IPv6 mapping.
|
|
* @param {string} ipv4 - IPv4 subnet in CIDR (e.g., "192.168.20.0/24")
|
|
* @param {string} ula_prefix - ULA prefix (e.g., "fd52:425:78eb::/48")
|
|
* @returns {{ipv6subnet: string, ipv6gateway: string}} IPv6 config with subnet/gateway
|
|
*/
|
|
deriveUlaFromIpv4(ipv4, ula_prefix) {
|
|
const ipv4Address = ipv4.split('/')[0];
|
|
const octets = ipv4Address.split('.').map(Number);
|
|
const subnetIdHex = ((octets[2] << 8) | octets[3]).toString(16).padStart(4, '0');
|
|
const ulaAddress = ula_prefix.split('/')[0];
|
|
const ulaParts = ulaAddress.split('::');
|
|
|
|
let hextets = ulaParts[0].split(':');
|
|
if (hextets.length === 1 && hextets[0] === "") {
|
|
hextets = [];
|
|
}
|
|
|
|
while (hextets.length < 3) {
|
|
hextets.push('0');
|
|
}
|
|
|
|
const ipv6SubnetAddress = `${hextets.slice(0, 3).join(':')}:${subnetIdHex}::`;
|
|
|
|
return {
|
|
ipv6subnet: `${ipv6SubnetAddress}/64`,
|
|
ipv6gateway: `${ipv6SubnetAddress}1`
|
|
};
|
|
},
|
|
});
|
|
|
|
const UtilIpv4 = baseclass.extend({
|
|
// >>> 0 forces unsigned 32-bit interpretation (JS bitwise ops are signed)
|
|
_ipToInt(octets) {
|
|
return ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
|
|
},
|
|
|
|
_intToIp(n) {
|
|
return [(n >>> 24) & 0xFF, (n >>> 16) & 0xFF, (n >>> 8) & 0xFF, n & 0xFF].join('.');
|
|
},
|
|
|
|
/**
|
|
* Convert a CIDR string to a start/end IP range.
|
|
* Works for both network addresses (10.89.0.0/24) and host addresses
|
|
* within a subnet (10.89.0.5/24) - the mask normalises either to the
|
|
* full network range.
|
|
* @param {string} cidr - CIDR notation (e.g., "10.89.0.128/25")
|
|
* @returns {{start_ip: string, end_ip: string}|null} Range or null if invalid
|
|
*/
|
|
cidrToRange(cidr) {
|
|
if (!cidr || typeof cidr !== 'string') return null;
|
|
|
|
const parts = cidr.split('/');
|
|
if (parts.length !== 2) return null;
|
|
|
|
const octets = parts[0].split('.').map(Number);
|
|
const prefixLen = parseInt(parts[1], 10);
|
|
|
|
if (octets.length !== 4 || octets.some(o => isNaN(o) || o < 0 || o > 255)) return null;
|
|
if (isNaN(prefixLen) || prefixLen < 0 || prefixLen > 32) return null;
|
|
|
|
const ipInt = this._ipToInt(octets);
|
|
const mask = prefixLen === 0 ? 0 : (0xFFFFFFFF << (32 - prefixLen)) >>> 0;
|
|
|
|
return {
|
|
start_ip: this._intToIp((ipInt & mask) >>> 0),
|
|
end_ip: this._intToIp((ipInt | (~mask >>> 0)) >>> 0),
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Return the first usable host address in a subnet (network address + 1).
|
|
* Conventionally used as the gateway address.
|
|
* @param {string} cidr - CIDR notation (e.g., "10.89.0.0/24")
|
|
* @returns {string|null} First host IP (e.g., "10.89.0.1") or null if invalid
|
|
*/
|
|
firstHost(cidr) {
|
|
const range = this.cidrToRange(cidr);
|
|
if (!range) return null;
|
|
|
|
const octets = range.start_ip.split('.').map(Number);
|
|
const next = (this._ipToInt(octets) + 1) >>> 0;
|
|
|
|
return this._intToIp(next);
|
|
},
|
|
});
|
|
|
|
const UtilFormat = baseclass.extend({
|
|
__name__: 'Podman.Util.Format',
|
|
|
|
/**
|
|
* Format bytes to human-readable size
|
|
*/
|
|
bytes(bytes, decimals) {
|
|
if (!bytes) return '0 B';
|
|
|
|
decimals = decimals ?? 2;
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
return parseFloat((bytes / k ** i).toFixed(decimals)) + ' ' + sizes[i];
|
|
},
|
|
|
|
/**
|
|
* Format timestamp to locale string with leading zeros
|
|
*/
|
|
date(timestamp) {
|
|
if (!timestamp) {
|
|
return _('Never');
|
|
}
|
|
|
|
let date;
|
|
|
|
if (typeof timestamp === 'string') {
|
|
if (timestamp === '0001-01-01T00:00:00Z' || timestamp.startsWith('0001-')) {
|
|
return _('Never');
|
|
}
|
|
date = new Date(timestamp);
|
|
} else {
|
|
date = new Date(timestamp * 1000);
|
|
}
|
|
|
|
if (isNaN(date.getTime())) {
|
|
return _('Unknown');
|
|
}
|
|
|
|
if (date.getFullYear() < 1970) {
|
|
return _('Never');
|
|
}
|
|
|
|
const day = ('0' + date.getDate()).slice(-2);
|
|
const month = ('0' + (date.getMonth() + 1)).slice(-2);
|
|
const year = date.getFullYear();
|
|
const hours = ('0' + date.getHours()).slice(-2);
|
|
const minutes = ('0' + date.getMinutes()).slice(-2);
|
|
const seconds = ('0' + date.getSeconds()).slice(-2);
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
},
|
|
|
|
/**
|
|
* Parse memory string to bytes
|
|
* Supports formats: 512m, 1g, 2gb, 1024, etc.
|
|
* @param {string} memStr - Memory string (e.g., "512m", "1g", "2gb")
|
|
* @returns {number} Size in bytes
|
|
*/
|
|
parseMemory(memStr) {
|
|
if (!memStr) return 0;
|
|
|
|
const match = memStr.match(/^(\d+(?:\.\d+)?)\s*([kmgt]?b?)?$/i);
|
|
if (!match) return 0;
|
|
|
|
const value = parseFloat(match[1]);
|
|
const unit = (match[2] || 'b').toLowerCase();
|
|
|
|
const multipliers = {
|
|
b: 1,
|
|
k: 1024,
|
|
kb: 1024,
|
|
m: 1024 * 1024,
|
|
mb: 1024 * 1024,
|
|
g: 1024 * 1024 * 1024,
|
|
gb: 1024 * 1024 * 1024,
|
|
t: 1024 * 1024 * 1024 * 1024,
|
|
tb: 1024 * 1024 * 1024 * 1024
|
|
};
|
|
|
|
return Math.floor(value * (multipliers[unit] || 1));
|
|
},
|
|
|
|
/**
|
|
* Parse duration string to nanoseconds (Podman format)
|
|
* Supports formats: 30s, 1m, 1h, 500ms, etc.
|
|
* @param {string} duration - Duration string (e.g., "30s", "1m", "1h")
|
|
* @returns {number} Duration in nanoseconds, or 0 if invalid
|
|
*/
|
|
parseDuration(duration) {
|
|
if (!duration) return 0;
|
|
|
|
const match = duration.match(/^(\d+(?:\.\d+)?)\s*(ns|us|ms|s|m|h)$/);
|
|
if (!match) return 0;
|
|
|
|
const value = parseFloat(match[1]);
|
|
const unit = match[2];
|
|
|
|
const multipliers = {
|
|
ns: 1,
|
|
us: 1000,
|
|
ms: 1000000,
|
|
s: 1000000000,
|
|
m: 60000000000,
|
|
h: 3600000000000
|
|
};
|
|
|
|
return Math.floor(value * (multipliers[unit] || 0));
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Shared utility functions.
|
|
*/
|
|
return baseclass.extend({
|
|
ipv4: new UtilIpv4(),
|
|
|
|
ipv6: new UtilIpv6(),
|
|
|
|
format: new UtilFormat(),
|
|
|
|
truncate(str, maxLength) {
|
|
if (!str || str.length <= maxLength) {
|
|
return str;
|
|
}
|
|
|
|
return str.substring(0, maxLength) + '...';
|
|
},
|
|
|
|
moveArrayItem(arr, fromIndex, toIndex) {
|
|
const [item] = arr.splice(fromIndex, 1);
|
|
arr.splice(toIndex, 0, item);
|
|
return arr;
|
|
},
|
|
|
|
_p(stringPlural) {
|
|
return this._n(2, '', stringPlural);
|
|
},
|
|
|
|
_n(length, stringSingular, stringPlural) {
|
|
return parseInt(length) === 1 ?
|
|
stringSingular : stringPlural;
|
|
},
|
|
});
|