feat(users): implement user management features and database schema
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-image (push) Successful in 1m57s
Docker images / frontend-image (push) Successful in 4m8s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 44s
Docker images / publish-release (push) Successful in 11s

Added user management functionality, including the creation of app_users and user_interface_bindings tables in the database. Implemented API routes for user data retrieval and permissions handling. Enhanced the traffic monitoring system to include user traffic statistics and interface bindings. Updated relevant components and services to support the new user features, improving overall application functionality and user experience.
This commit is contained in:
Denozordec committed 2026-09-06 19:20:05 +07:00
1 parent fe32c9313a
commit b3e50a1f5f
34 files changed
+2883 -1158

No files matched your search

+51 -5
View File
@@ -98,7 +98,7 @@ export function buildTrafficFromSamples(
rows: TrafficSampleLike[],
rangeStartMs: number,
rangeEndMs: number,
onlyInterface?: string,
onlyInterface?: string | readonly string[],
): BuiltTrafficSeries {
const empty: BuiltTrafficSeries = {
rxNow: 0,
@@ -120,6 +120,10 @@ export function buildTrafficFromSamples(
byIface.set(r.interfaceName, arr)
}
const allowList = Array.isArray(onlyInterface)
? onlyInterface
: (typeof onlyInterface === "string" ? [onlyInterface] : null)
const rxPoints: Array<{ t: number; v: number }> = []
const txPoints: Array<{ t: number; v: number }> = []
const byTs = new Map<number, { rx: number; tx: number }>()
@@ -129,8 +133,8 @@ export function buildTrafficFromSamples(
let sessions = 0
for (const [name, arr] of byIface) {
if (onlyInterface) {
if (name !== onlyInterface) continue
if (allowList) {
if (!allowList.includes(name)) continue
} else if (isLoopbackName(name)) {
continue
}
@@ -138,7 +142,7 @@ export function buildTrafficFromSamples(
const sorted = [...arr].sort((a, b) => a.sampledAt.localeCompare(b.sampledAt))
const last = sorted[sorted.length - 1]
if (!last) continue
if (!onlyInterface && (!last.running || last.disabled)) continue
if (!allowList && (!last.running || last.disabled)) continue
if (last.running && !last.disabled) sessions += 1
@@ -154,7 +158,7 @@ export function buildTrafficFromSamples(
const prev = sorted[i - 1]
const cur = sorted[i]
if (!prev || !cur) continue
if (!onlyInterface && (!cur.running || cur.disabled)) continue
if (!allowList && (!cur.running || cur.disabled)) continue
const t0 = parseIsoMs(prev.sampledAt)
const t1 = parseIsoMs(cur.sampledAt)
const rxBps = rateBpsFromDelta(prev.rxBytes, cur.rxBytes, t0, t1)
@@ -194,6 +198,48 @@ export function buildTrafficFromSamples(
}
}
export function mergeBuiltTraffic(parts: BuiltTrafficSeries[]): BuiltTrafficSeries {
const empty: BuiltTrafficSeries = {
rxNow: 0,
txNow: 0,
rxPeak: 0,
txPeak: 0,
rxTotalGiB: 0,
txTotalGiB: 0,
sessions: 0,
rxSeries: Array.from({ length: SERIES_POINTS }, () => 0),
txSeries: Array.from({ length: SERIES_POINTS }, () => 0),
}
if (parts.length === 0) return empty
const acc = {
rxNow: 0,
txNow: 0,
rxPeak: 0,
txPeak: 0,
rxTotalGiB: 0,
txTotalGiB: 0,
sessions: 0,
rxSeries: Array.from({ length: SERIES_POINTS }, () => 0),
txSeries: Array.from({ length: SERIES_POINTS }, () => 0),
}
for (const p of parts) {
acc.rxNow += p.rxNow
acc.txNow += p.txNow
acc.rxPeak += p.rxPeak
acc.txPeak += p.txPeak
acc.rxTotalGiB += p.rxTotalGiB
acc.txTotalGiB += p.txTotalGiB
acc.sessions += p.sessions
for (let i = 0; i < SERIES_POINTS; i++) {
acc.rxSeries[i] += p.rxSeries[i] ?? 0
acc.txSeries[i] += p.txSeries[i] ?? 0
}
}
acc.rxTotalGiB = Number(acc.rxTotalGiB.toFixed(1))
acc.txTotalGiB = Number(acc.txTotalGiB.toFixed(1))
return acc
}
export function parseMonitorTraffic(
raw: unknown,
opts?: { onlyInterface?: string },