From 5ec78f8fc56a4de998eb1558e96be5ceb93250c7 Mon Sep 17 00:00:00 2001 From: "e.khalilov" Date: Fri, 3 Oct 2025 19:28:36 +0300 Subject: [PATCH 1/4] clear dead devices --- lib/cli/local/index.js | 8 +- lib/cli/reaper/index.js | 6 + lib/db/models/all/model.js | 14 +- lib/db/models/device/model.js | 15 +- lib/db/models/group/model.js | 2 +- lib/units/api/controllers/devices.js | 195 ++++++------------ lib/units/api/paths/devices/groups/{id}.js | 6 +- .../api/paths/devices/{serial}/groups/{id}.js | 6 +- lib/units/api/paths/groups/{id}/teams.js | 3 +- lib/units/api/paths/team.js | 3 +- lib/units/api/paths/team/{id}.js | 3 +- lib/units/api/paths/team/{id}/delete.js | 3 +- .../api/paths/team/{id}/group/{group}.js | 3 +- lib/units/api/paths/team/{id}/user/{email}.js | 3 +- lib/units/api/paths/teams.js | 3 +- lib/units/api/paths/user/{email}/teams.js | 3 +- lib/units/api/swagger/api_v1.yaml | 8 +- lib/units/processor/index.ts | 21 +- lib/units/reaper/index.ts | 72 +++++-- lib/wire/wire.proto | 4 + lib/wire/wire.ts | 56 +++++ .../types/addDeviceToOriginGroupParams.ts | 14 ++ ui/src/generated/types/index.ts | 2 + .../removeDeviceFromOriginGroupParams.ts | 14 ++ 24 files changed, 281 insertions(+), 186 deletions(-) create mode 100644 ui/src/generated/types/addDeviceToOriginGroupParams.ts create mode 100644 ui/src/generated/types/removeDeviceFromOriginGroupParams.ts diff --git a/lib/cli/local/index.js b/lib/cli/local/index.js index 5acc202e36..14839e2db9 100644 --- a/lib/cli/local/index.js +++ b/lib/cli/local/index.js @@ -302,6 +302,11 @@ export const builder = function(yargs) { type: 'boolean', default: true }) + .option('time-to-device-cleanup', { + describe: 'Time in seconds after which connected devices should be deleted. 0 - do not delete', + type: 'number', + default: 0 + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_LOCAL_` (e.g. ' + @@ -343,7 +348,8 @@ export const handler = function(argv) { [ // reaper one 'reaper', 'reaper001', '--connect-push', argv.bindDevPull, - '--connect-sub', argv.bindDevPub + '--connect-sub', argv.bindDevPub, + '--time-to-device-cleanup', argv.timeToDeviceCleanup ], [ // provider 'provider', diff --git a/lib/cli/reaper/index.js b/lib/cli/reaper/index.js index 651889b528..a7b2da6ebb 100644 --- a/lib/cli/reaper/index.js +++ b/lib/cli/reaper/index.js @@ -30,6 +30,11 @@ export const builder = function(yargs) { type: 'string', default: os.hostname() }) + .option('time-to-device-cleanup', { + describe: 'Time in seconds after which connected devices should be deleted. 0 - do not delete', + type: 'number', + default: 0 + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_REAPER_` (e.g. ' + @@ -39,6 +44,7 @@ export const handler = function(argv) { return reaper({ name: argv.name, heartbeatTimeout: argv.heartbeatTimeout, + timeToDeviceCleanup: argv.timeToDeviceCleanup, endpoints: { push: argv.connectPush, sub: argv.connectSub diff --git a/lib/db/models/all/model.js b/lib/db/models/all/model.js index 1082680ac9..0dd60610b3 100644 --- a/lib/db/models/all/model.js +++ b/lib/db/models/all/model.js @@ -9,7 +9,7 @@ import {v4 as uuidv4} from 'uuid' import * as apiutil from '../../../util/apiutil.js' import GroupModel from '../group/index.js' import UserModel from '../user/index.js' - +import DeviceModel from '../device/index.js' import logger from '../../../util/logger.js' import {getRootGroup, getGroup} from '../group/model.js' @@ -887,16 +887,6 @@ export const loadStandardDevices = function(groups, fields) { }, fields) } -// dbapi.loadPresentDevices = function() { -export const loadPresentDevices = function() { - return db.devices.find({present: true}).toArray() -} - -// dbapi.loadDeviceBySerial = function(serial) { -export const loadDeviceBySerial = function(serial) { - return findDevice({serial: serial}) -} - // dbapi.loadDevice = function(groups, serial) { export const loadDevice = function(groups, serial) { return findDevice({ @@ -1186,7 +1176,7 @@ export const setAbsentDisconnectedDevices = function() { // dbapi.getInstalledApplications = function(message) { export const getInstalledApplications = function(message) { - return loadDeviceBySerial(message.serial) + return DeviceModel.loadDeviceBySerial(message.serial) } // dbapi.setDeviceType = function(serial, type) { diff --git a/lib/db/models/device/model.js b/lib/db/models/device/model.js index ef29f27e49..b00c715014 100644 --- a/lib/db/models/device/model.js +++ b/lib/db/models/device/model.js @@ -32,6 +32,10 @@ export const loadPresentDevices = function() { return db.devices.find({present: true}).toArray() } +export const loadDeviceBySerial = function(serial) { + return db.devices.findOne({serial}) +} + // dbapi.loadDevicesBySerials = function(serials) { export const loadDevicesBySerials = function(serials) { return db.devices.find({serial: {$in: serials}}).toArray() @@ -97,6 +101,15 @@ export const getDeviceType = function(serial) { }) } +export const getDeadDevice = function(time = 60_000) { + return db.devices.find( + { + present: false, + presenceChangedAt: {$lt: new Date(Date.now() - time)} + } + ).project({serial: 1, present: 1, presenceChangedAt: 1}).toArray() +} + // dbapi.generateIndexes = function() { export const generateIndexes = function() { db.devices.createIndex({serial: -1}).then((result) => { @@ -111,8 +124,6 @@ export const generateIndexes = function() { */ - - // dbapi.deleteDevice = function(serial) { export const deleteDevice = function(serial) { return db.devices.deleteOne({serial: serial}) diff --git a/lib/db/models/group/model.js b/lib/db/models/group/model.js index a68fd84eb8..045093bc58 100644 --- a/lib/db/models/group/model.js +++ b/lib/db/models/group/model.js @@ -568,7 +568,7 @@ export const deleteUserGroup = async(id) => { await db.groups.deleteOne({id}) await Promise.all( - group?.users.map(email => db.groups.updateOne( + group?.users.map(email => db.users.updateOne( {email} , { $pull: {'groups.subscribed': id} diff --git a/lib/units/api/controllers/devices.js b/lib/units/api/controllers/devices.js index 7d9f916420..41b55eb21b 100644 --- a/lib/units/api/controllers/devices.js +++ b/lib/units/api/controllers/devices.js @@ -4,19 +4,12 @@ import dbapi from '../../../db/api.js' import logger from '../../../util/logger.js' import * as apiutil from '../../../util/apiutil.js' import * as lockutil from '../../../util/lockutil.js' -import util from 'util' import {v4 as uuidv4} from 'uuid' -import wire from '../../../wire/index.js' -import wireutil from '../../../wire/util.js' -import {WireRouter} from '../../../wire/router.js' import * as fake from '../../../util/fakedevice.js' -import deviceutil from '../../../util/deviceutil.js' -import * as jwtutil from '../../../util/jwtutil.js' import useDevice, {UseDeviceError} from '../helpers/useDevice.js' import * as Sentry from '@sentry/node' import {accessTokenAuth} from '../helpers/securityHandlers.js' -import {DeviceOriginGroupMessage} from '../../../wire/wire.js' -var log = logger.createLogger('api:controllers:devices') +const log = logger.createLogger('api:controllers:devices') /* ------------------------------------ PRIVATE FUNCTIONS ------------------------------- */ function filterGenericDevices(req, res, devices) { @@ -343,141 +336,77 @@ function getDeviceBookings(req, res) { apiutil.internalError(res, 'Failed to get device bookings: ', err.stack) }) } -function addOriginGroupDevices(req, res) { - const serials = apiutil.getBodyParameter(req.body, 'serials') - const fields = apiutil.getQueryParameter(req.query.fields) - const target = apiutil.getQueryParameter(req.query.redirected) ? 'device' : 'devices' - async function askUpdateDeviceOriginGroup(group, serial) { - log.info('[addOriginGroupDevices] Ask to update device origin group [serial: "%s", group: "%s"]', serial, group.id) - const signature = util.format('%s', uuidv4()).replace(/-/g, '') - const result = await dbapi.updateDeviceOriginGroup(serial, group, signature) +// Note: Device lists in groups change automatically +const addDeviceToOriginGroup = async(req, res) => { + try { + const serial = req.query.serial || req.params.serial + const fields = req.query.fields - if (result) { - log.info('[addOriginGroupDevices] Successfully updated device origin group [serial: "%s", group: "%s"]', serial, group.id) + if (!serial?.length) { + throw new Error('serial is not specified') + } - const device = await dbapi.loadDeviceBySerial(serial) - if (fields) { - return _.pick(apiutil.publishDevice(device, req), fields.split(',')) - } + const targetGroup = await dbapi.getUserGroup(req.user.email, req.params.id) + if (!targetGroup) { + throw new Error('target group not found') + } - return apiutil.publishDevice(device, req) + const stats = await dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial) + if (stats.modifiedCount === 0) { + return apiutil.lightComputeStats(res, stats) } - } - function updateDeviceOriginGroup(group, serial) { - log.info('[addOriginGroupDevices] Add device %s to origin group %s', serial, group.id) - const lock = {} - return dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial).then(function(stats) { - if (stats.modifiedCount === 0) { - return apiutil.lightComputeStats(res, stats) - } - lock.device = stats.changes[0].new_val - return dbapi.isUpdateDeviceOriginGroupAllowed(serial, group) - .then(function(updatingAllowed) { - if (!updatingAllowed) { - log.error('[addOriginGroupDevices] Device group editing is not allowed %s', serial, group.id) - apiutil.respond(res, 403, 'Forbidden (device is currently booked)') - return Promise.reject('booked') - } - return askUpdateDeviceOriginGroup(group, serial) - }) - }) - .finally(function() { - lockutil.unlockDevice(lock) - }) - } - function updateDevicesOriginGroup(group, serials) { - let results = [] - return Promise.each(serials, function(serial) { - return updateDeviceOriginGroup(group, serial).then(function(result) { - results.push(result) - }) - }) - .then(function() { + const signature = uuidv4().replace(/-/g, '') + await dbapi.updateDeviceOriginGroup(serial, targetGroup, signature) - /** @type {any} */ - const result = target === 'device' ? {device: {}} : {devices: []} - results = _.without(results, 'unchanged') - if (!results.length) { - return apiutil.respond(res, 200, `Unchanged (${target})`, result) - } - results = _.without(results, 'not found') - if (!results.length) { - return apiutil.respond(res, 404, `Not Found (${target})`) - } - if (target === 'device') { - result.device = results[0] - } - else { - result.devices = results - } - return apiutil.respond(res, 200, `Updated (${target})`, result) - }) - .catch(function(err) { - if (err !== 'booked' && err !== 'timeout' && err !== 'busy') { - throw err - } - }) + lockutil.unlockDevice({device: stats.changes[0].new_val}) + + const device = await dbapi.loadDeviceBySerial(serial) + return apiutil.respond(res, 200, 'Updated (device)', apiutil.publishDevice(device, req)) } + catch (/** @type {any} */e) { + log.error('[addDeviceToOriginGroup] failed to update device origin group: %s', e?.message) + apiutil.internalError('Failed to update device origin group') + } +} - return dbapi.getUserGroup(req.user.email, req.params.id).then(function(group) { - log.info('[addOriginGroupDevices] Add devices to origin group with id: %s [%s | %s]', group?.id, group?.name, group?.class) - if (!group) { - return false - } +// Note: Device lists in groups change automatically +const removeDeviceFromOriginGroup = async(req, res) => { + try { + const serial = req.query.serial + const fields = req.query.fields - if (!apiutil.isOriginGroup(group.class)) { - return apiutil.respond(res, 400, 'Bad Request (this group cannot act as an origin one)') - } - if (typeof serials !== 'undefined') { - return updateDevicesOriginGroup(group, _.without(serials.split(','), '').filter(function(serial) { - return group.devices.indexOf(serial) < 0 - })) + if (!serial?.length) { + throw new Error('serial is not specified') } - log.info('[addOriginGroupDevices] Serials list is not specified') - return dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) { - if (group.class === apiutil.BOOKABLE) { - return devices - } - return extractStandardizableDevices(devices) - }) - .then(function(devices) { - const serials = [] - devices.forEach(function(device) { - if (group.devices.indexOf(device.serial) < 0) { - serials.push(device.serial) - } - }) - return updateDevicesOriginGroup(group, serials) - }) - }) - .catch(function(err) { - apiutil.internalError(res, `Failed to update ${target} origin group: `, err.stack) - }) -} -function addOriginGroupDevice(req, res) { - apiutil.redirectApiWrapper('serial', addOriginGroupDevices, req, res) -} -function removeOriginGroupDevices(req, res) { - return dbapi.getUserGroup(req.user.email, req.params.id).then(function(group) { - if (!group) { - return false + const targetGroup = await dbapi.getRootGroup() + if (!targetGroup) { + throw new Error('target group not found') } - if (!apiutil.checkBodyParameter(req.body, 'serials')) { - req.body = {serials: group.devices.join()} + const lock = {} + const stats = await dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial) + if (stats.modifiedCount === 0) { + return apiutil.lightComputeStats(res, stats) } - return dbapi.getRootGroup().then(function(group) { - req.params.id = group?.id - return addOriginGroupDevices(req, res) - }) - }) -} -function removeOriginGroupDevice(req, res) { - apiutil.redirectApiWrapper('serial', removeOriginGroupDevices, req, res) + lock.device = stats.changes[0].new_val + + const signature = uuidv4().replace(/-/g, '') + await dbapi.updateDeviceOriginGroup(serial, targetGroup, signature) + + lockutil.unlockDevice(lock) + + const device = await dbapi.loadDeviceBySerial(serial) + return apiutil.respond(res, 200, 'Updated (device)', apiutil.publishDevice(device, req)) + } + catch (/** @type {any} */e) { + log.error('[addDeviceToOriginGroup] failed to update device origin group: %s', e?.message) + apiutil.internalError('Failed to update device origin group') + } } + function putDeviceInfoBySerial(req, res) { const serial = req.params.serial const body = req.body @@ -694,10 +623,8 @@ export {getDeviceOwner} export {getDeviceGroups} export {getDeviceType} export {getDeviceBookings} -export {addOriginGroupDevice} -export {addOriginGroupDevices} -export {removeOriginGroupDevice} -export {removeOriginGroupDevices} +export {addDeviceToOriginGroup} +export {removeDeviceFromOriginGroup} export {deleteDevice} export {deleteDevices} export {updateStorageInfo} @@ -714,10 +641,8 @@ export default { getDeviceGroups: getDeviceGroups, getDeviceType: getDeviceType, getDeviceBookings: getDeviceBookings, - addOriginGroupDevice: addOriginGroupDevice, - addOriginGroupDevices: addOriginGroupDevices, - removeOriginGroupDevice: removeOriginGroupDevice, - removeOriginGroupDevices: removeOriginGroupDevices, + addDeviceToOriginGroup: addDeviceToOriginGroup, + removeDeviceFromOriginGroup: removeDeviceFromOriginGroup, deleteDevice: deleteDevice, deleteDevices: deleteDevices, updateStorageInfo: updateStorageInfo, diff --git a/lib/units/api/paths/devices/groups/{id}.js b/lib/units/api/paths/devices/groups/{id}.js index 584cc9ba37..aae5e15e46 100644 --- a/lib/units/api/paths/devices/groups/{id}.js +++ b/lib/units/api/paths/devices/groups/{id}.js @@ -1,15 +1,15 @@ // Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY // Generated for controller devices -import {addOriginGroupDevices, removeOriginGroupDevices} from '../../../controllers/devices.js' +import {addDeviceToOriginGroup, removeDeviceFromOriginGroup} from '../../../controllers/devices.js' export function put(req, res) { - return addOriginGroupDevices(req, res) + return addDeviceToOriginGroup(req, res) } export function del(req, res) { - return removeOriginGroupDevices(req, res) + return removeDeviceFromOriginGroup(req, res) } diff --git a/lib/units/api/paths/devices/{serial}/groups/{id}.js b/lib/units/api/paths/devices/{serial}/groups/{id}.js index 8c9cd0c583..f77a44014c 100644 --- a/lib/units/api/paths/devices/{serial}/groups/{id}.js +++ b/lib/units/api/paths/devices/{serial}/groups/{id}.js @@ -1,15 +1,15 @@ // Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY // Generated for controller devices -import {addOriginGroupDevice, removeOriginGroupDevice} from '../../../../controllers/devices.js' +import {addDeviceToOriginGroup, removeDeviceFromOriginGroup} from '../../../../controllers/devices.js' export function put(req, res) { - return addOriginGroupDevice(req, res) + return addDeviceToOriginGroup(req, res) } export function del(req, res) { - return removeOriginGroupDevice(req, res) + return removeDeviceFromOriginGroup(req, res) } diff --git a/lib/units/api/paths/groups/{id}/teams.js b/lib/units/api/paths/groups/{id}/teams.js index ec99becf57..e6dd871d80 100644 --- a/lib/units/api/paths/groups/{id}/teams.js +++ b/lib/units/api/paths/groups/{id}/teams.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {getGroupsTeams} from '../../../controllers/teams.js' diff --git a/lib/units/api/paths/team.js b/lib/units/api/paths/team.js index d8312985e7..31d94d524b 100644 --- a/lib/units/api/paths/team.js +++ b/lib/units/api/paths/team.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {createTeam} from '../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}.js b/lib/units/api/paths/team/{id}.js index 0227d69241..dc185b3d5b 100644 --- a/lib/units/api/paths/team/{id}.js +++ b/lib/units/api/paths/team/{id}.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {getTeamById, updateTeam, removeFromTeam} from '../../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}/delete.js b/lib/units/api/paths/team/{id}/delete.js index b2c1ca799a..48da77cdec 100644 --- a/lib/units/api/paths/team/{id}/delete.js +++ b/lib/units/api/paths/team/{id}/delete.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {deleteTeam} from '../../../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}/group/{group}.js b/lib/units/api/paths/team/{id}/group/{group}.js index 1784fe3537..63038806a8 100644 --- a/lib/units/api/paths/team/{id}/group/{group}.js +++ b/lib/units/api/paths/team/{id}/group/{group}.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {removeGroupFromTeam} from '../../../../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}/user/{email}.js b/lib/units/api/paths/team/{id}/user/{email}.js index 9b60901df0..53120ab094 100644 --- a/lib/units/api/paths/team/{id}/user/{email}.js +++ b/lib/units/api/paths/team/{id}/user/{email}.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {removeUserFromTeam} from '../../../../controllers/teams.js' diff --git a/lib/units/api/paths/teams.js b/lib/units/api/paths/teams.js index 1b7a3cacf1..953c2467c9 100644 --- a/lib/units/api/paths/teams.js +++ b/lib/units/api/paths/teams.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {getTeams} from '../controllers/teams.js' diff --git a/lib/units/api/paths/user/{email}/teams.js b/lib/units/api/paths/user/{email}/teams.js index 6e8dc21786..537b227911 100644 --- a/lib/units/api/paths/user/{email}/teams.js +++ b/lib/units/api/paths/user/{email}/teams.js @@ -1,4 +1,5 @@ -// teams +// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY +// Generated for controller teams import {getUserTeams} from '../../../controllers/teams.js' diff --git a/lib/units/api/swagger/api_v1.yaml b/lib/units/api/swagger/api_v1.yaml index 1f6f2ed149..074cf880de 100644 --- a/lib/units/api/swagger/api_v1.yaml +++ b/lib/units/api/swagger/api_v1.yaml @@ -2759,7 +2759,7 @@ paths: summary: Adds devices into an origin group description: Adds devices into an origin group along with updating each added device; returns the updated devices - operationId: addOriginGroupDevices + operationId: addDeviceToOriginGroup parameters: - name: id in: path @@ -2815,7 +2815,7 @@ paths: summary: Removes devices from an origin group description: Removes devices from an origin group along with updating each removed device; returns the updated devices - operationId: removeOriginGroupDevices + operationId: removeDeviceFromOriginGroup parameters: - name: id in: path @@ -3045,7 +3045,7 @@ paths: summary: Adds a device into an origin group description: Adds a device into an origin group along with updating the added device; returns the updated device - operationId: addOriginGroupDevice + operationId: addDeviceToOriginGroup parameters: - name: serial in: path @@ -3086,7 +3086,7 @@ paths: summary: Removes a device from an origin group description: Removes a device from an origin group along with updating the removed device; returns the updated device - operationId: removeOriginGroupDevice + operationId: removeDeviceFromOriginGroup parameters: - name: serial in: path diff --git a/lib/units/processor/index.ts b/lib/units/processor/index.ts index e98df9ab83..2ba3f911f3 100644 --- a/lib/units/processor/index.ts +++ b/lib/units/processor/index.ts @@ -9,6 +9,7 @@ import lifecycle from '../../util/lifecycle.js' import srv from '../../util/srv.js' import * as zmqutil from '../../util/zmqutil.js' import UserModel from '../../db/models/user/index.js' +import DeviceModel from '../../db/models/device/index.js' import { UserChangeMessage, GroupChangeMessage, @@ -53,8 +54,9 @@ import { DeleteDevice, SetAbsentDisconnectedDevices, GetServicesAvailabilityMessage, - DeviceRegisteredMessage, GetPresentDevices, DeviceGetIsInOrigin + DeviceRegisteredMessage, GetPresentDevices, DeviceGetIsInOrigin, GetDeadDevices } from '../../wire/wire.js' +import {getDeadDevice} from "../../db/models/device/model.js"; interface Options { name: string @@ -228,8 +230,8 @@ export default db.ensureConnectivity(async(options: Options) => { appDealer.send([channel, data]) }) .on(DeviceGetIsInOrigin, async (channel, message) => { - const device = await dbapi.loadDeviceBySerial(message.serial) - const isInOrigin = device.group.id === device.group.origin + const device = await DeviceModel.loadDeviceBySerial(message.serial) + const isInOrigin = device ? device.group.id === device.group.origin : false devDealer.send([ channel, reply.okay('success', {isInOrigin}) @@ -289,7 +291,7 @@ export default db.ensureConnectivity(async(options: Options) => { appDealer.send([channel, data]) }) .on(GetPresentDevices, async (channel, message, data) => { - const devices = await dbapi.loadPresentDevices() + const devices = await DeviceModel.loadPresentDevices() .then(devices => devices.map(d => d.serial)) devDealer.send([ channel, @@ -299,6 +301,17 @@ export default db.ensureConnectivity(async(options: Options) => { .on(DeviceHeartbeatMessage, (channel, message, data) => { devDealer.send([ channel, data ]) }) + .on(GetDeadDevices, async(channel, message, data) => { + const deadDevices = await DeviceModel.getDeadDevice(message.time) + console.log('govno', {deadDevices}) + devDealer.send([ + channel, + reply.okay('success', {deadDevices}) + ]) + }) + .on(DeleteDevice, async(channel, message, data) => { + DeviceModel.deleteDevice(message.serial) + }) .handler(); devDealer.on('message', router) diff --git a/lib/units/reaper/index.ts b/lib/units/reaper/index.ts index 0db7898f36..23dc3a5cf8 100644 --- a/lib/units/reaper/index.ts +++ b/lib/units/reaper/index.ts @@ -1,8 +1,9 @@ import logger from '../../util/logger.js' import { + DeleteDevice, DeviceAbsentMessage, DeviceHeartbeatMessage, - DeviceIntroductionMessage, DevicePresentMessage, + DeviceIntroductionMessage, DevicePresentMessage, GetDeadDevices, GetPresentDevices } from '../../wire/wire.js' import wireutil from '../../wire/util.js' @@ -17,6 +18,7 @@ const log = logger.createLogger('reaper') interface Options { heartbeatTimeout: number + timeToDeviceCleanup: number // in seconds endpoints: { sub: string[] push: string[] @@ -87,21 +89,65 @@ export default (async(options: Options) => { ttlset.stop() }) + const router = new WireRouter() + .on(DeviceIntroductionMessage, (channel, message) => { + ttlset.drop(message.serial, TTLSet.SILENT) + ttlset.bump(message.serial, Date.now()) + }) + .on(DeviceHeartbeatMessage, (channel, message) => { + ttlset.bump(message.serial, Date.now()) + }) + .on(DeviceAbsentMessage, (channel, message) => { + ttlset.drop(message.serial, TTLSet.SILENT) + }) + + if (options.timeToDeviceCleanup) { + log.info('deviceCleanerLoop enabled') + + // This functionality is implemented in the Reaper unit because this unit cannot be replicated + const deviceCleanerLoop = async() => { + try { + await new Promise(r => setTimeout(r, 120_000)) // 2 min delay + const absenceDuration = options.timeToDeviceCleanup + const {deadDevices} = await runTransactionDev(wireutil.global, GetDeadDevices, { + time: options.timeToDeviceCleanup * 1000 + }, {sub, push, router}) + + for (const {serial, present} of deadDevices) { + if (present) { + continue + } + + log.info( // @ts-ignore + 'Removing a dead device [serial: %s, absence_duration: %.1f %s]', + serial, + ... ( + absenceDuration >= 3600 // if more 1 hour + ? [absenceDuration / 3600, 'hrs'] + : absenceDuration >= 60 // if more 1 minute + ? [absenceDuration / 60, 'min'] + : [absenceDuration, 'sec'] + ) + ) + + push.send([ + wireutil.global, + wireutil.pack(DeleteDevice, {serial}) + ]) + } + } catch (err: any) { + log.error('Dead device check failed with error: %s', err?.message) + } finally { + return deviceCleanerLoop() + } + } + + deviceCleanerLoop() + } + try { log.info('Reaping devices with no heartbeat') - const router = new WireRouter() - .on(DeviceIntroductionMessage, (channel, message) => { - ttlset.drop(message.serial, TTLSet.SILENT) - ttlset.bump(message.serial, Date.now()) - }) - .on(DeviceHeartbeatMessage, (channel, message) => { - ttlset.bump(message.serial, Date.now()) - }) - .on(DeviceAbsentMessage, (channel, message) => { - ttlset.drop(message.serial, TTLSet.SILENT) - }) - // Listen to changes sub.on('message', router.handler()) diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index 53be0505e9..655b7138a8 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -909,3 +909,7 @@ message DeviceGetIsInOrigin { message GetPresentDevices { } + +message GetDeadDevices { + required uint32 time = 1; +} diff --git a/lib/wire/wire.ts b/lib/wire/wire.ts index 85edad04eb..438ab51313 100644 --- a/lib/wire/wire.ts +++ b/lib/wire/wire.ts @@ -2397,6 +2397,15 @@ export interface DeviceGetIsInOrigin { */ export interface GetPresentDevices { } +/** + * @generated from protobuf message GetDeadDevices + */ +export interface GetDeadDevices { + /** + * @generated from protobuf field: required uint32 time = 1 + */ + time: number; +} /** * @generated from protobuf enum DeviceStatus */ @@ -11744,3 +11753,50 @@ class GetPresentDevices$Type extends MessageType { * @generated MessageType for protobuf message GetPresentDevices */ export const GetPresentDevices = new GetPresentDevices$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class GetDeadDevices$Type extends MessageType { + constructor() { + super("GetDeadDevices", [ + { no: 1, name: "time", kind: "scalar", T: 13 /*ScalarType.UINT32*/ } + ]); + } + create(value?: PartialMessage): GetDeadDevices { + const message = globalThis.Object.create((this.messagePrototype!)); + message.time = 0; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetDeadDevices): GetDeadDevices { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* required uint32 time */ 1: + message.time = reader.uint32(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: GetDeadDevices, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* required uint32 time = 1; */ + if (message.time !== 0) + writer.tag(1, WireType.Varint).uint32(message.time); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message GetDeadDevices + */ +export const GetDeadDevices = new GetDeadDevices$Type(); diff --git a/ui/src/generated/types/addDeviceToOriginGroupParams.ts b/ui/src/generated/types/addDeviceToOriginGroupParams.ts new file mode 100644 index 0000000000..3219ad7eea --- /dev/null +++ b/ui/src/generated/types/addDeviceToOriginGroupParams.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.3.0 🍺 + * Do not edit manually. + * DeviceHub + * Control and manages real Smartphone devices from browser and restful apis + * OpenAPI spec version: 1.4.7 + */ + +export type AddDeviceToOriginGroupParams = { + /** + * Comma-seperated list of fields; only listed fields will be returned in response + */ + fields?: string +} diff --git a/ui/src/generated/types/index.ts b/ui/src/generated/types/index.ts index dd47e42104..5ffdf6dce2 100644 --- a/ui/src/generated/types/index.ts +++ b/ui/src/generated/types/index.ts @@ -12,6 +12,7 @@ export * from './adbKeyAddedResponse' export * from './adbPortResponse' export * from './adbRangeResponse' export * from './addAdbPublicKeyBody' +export * from './addDeviceToOriginGroupParams' export * from './addDevicesParams' export * from './addOriginGroupDevicesParams' export * from './addUserDevicePayload' @@ -108,6 +109,7 @@ export * from './groupState' export * from './groupsPayload' export * from './ownerResponse' export * from './remoteConnectUserDeviceResponse' +export * from './removeDeviceFromOriginGroupParams' export * from './removeOriginGroupDevicesParams' export * from './revokeAdmin200' export * from './serviceUserResponse' diff --git a/ui/src/generated/types/removeDeviceFromOriginGroupParams.ts b/ui/src/generated/types/removeDeviceFromOriginGroupParams.ts new file mode 100644 index 0000000000..eea3e0ec99 --- /dev/null +++ b/ui/src/generated/types/removeDeviceFromOriginGroupParams.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.3.0 🍺 + * Do not edit manually. + * DeviceHub + * Control and manages real Smartphone devices from browser and restful apis + * OpenAPI spec version: 1.4.7 + */ + +export type RemoveDeviceFromOriginGroupParams = { + /** + * Comma-seperated list of fields; only listed fields will be returned in response + */ + fields?: string +} From 645ef89b40b90ab9181987c3e4fbdfa8ed724446 Mon Sep 17 00:00:00 2001 From: "e.khalilov" Date: Mon, 6 Oct 2025 11:58:18 +0300 Subject: [PATCH 2/4] minor fix --- lib/cli/local/index.js | 8 ++++- lib/cli/reaper/index.js | 8 ++++- lib/units/processor/index.ts | 1 - lib/units/reaper/index.ts | 58 ++++++++++++++++++++---------------- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/lib/cli/local/index.js b/lib/cli/local/index.js index 14839e2db9..83e57c0b94 100644 --- a/lib/cli/local/index.js +++ b/lib/cli/local/index.js @@ -307,6 +307,11 @@ export const builder = function(yargs) { type: 'number', default: 0 }) + .option('device-cleanup-interval', { + describe: 'Interval for checking devices for cleanup in minutes', + type: 'number', + default: 120_000 + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_LOCAL_` (e.g. ' + @@ -349,7 +354,8 @@ export const handler = function(argv) { 'reaper', 'reaper001', '--connect-push', argv.bindDevPull, '--connect-sub', argv.bindDevPub, - '--time-to-device-cleanup', argv.timeToDeviceCleanup + '--time-to-device-cleanup', argv.timeToDeviceCleanup, + '--device-cleanup-interval', argv.deviceCleanupInterval ], [ // provider 'provider', diff --git a/lib/cli/reaper/index.js b/lib/cli/reaper/index.js index a7b2da6ebb..b741bc2214 100644 --- a/lib/cli/reaper/index.js +++ b/lib/cli/reaper/index.js @@ -31,10 +31,15 @@ export const builder = function(yargs) { default: os.hostname() }) .option('time-to-device-cleanup', { - describe: 'Time in seconds after which connected devices should be deleted. 0 - do not delete', + describe: 'Time in minutes after which connected devices should be deleted. 0 - do not delete', type: 'number', default: 0 }) + .option('device-cleanup-interval', { + describe: 'Interval for checking devices for cleanup in minutes', + type: 'number', + default: 2 + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_REAPER_` (e.g. ' + @@ -45,6 +50,7 @@ export const handler = function(argv) { name: argv.name, heartbeatTimeout: argv.heartbeatTimeout, timeToDeviceCleanup: argv.timeToDeviceCleanup, + deviceCleanupInterval: argv.deviceCleanupInterval, endpoints: { push: argv.connectPush, sub: argv.connectSub diff --git a/lib/units/processor/index.ts b/lib/units/processor/index.ts index 2ba3f911f3..0e4578293b 100644 --- a/lib/units/processor/index.ts +++ b/lib/units/processor/index.ts @@ -303,7 +303,6 @@ export default db.ensureConnectivity(async(options: Options) => { }) .on(GetDeadDevices, async(channel, message, data) => { const deadDevices = await DeviceModel.getDeadDevice(message.time) - console.log('govno', {deadDevices}) devDealer.send([ channel, reply.okay('success', {deadDevices}) diff --git a/lib/units/reaper/index.ts b/lib/units/reaper/index.ts index 23dc3a5cf8..2fde098619 100644 --- a/lib/units/reaper/index.ts +++ b/lib/units/reaper/index.ts @@ -18,7 +18,8 @@ const log = logger.createLogger('reaper') interface Options { heartbeatTimeout: number - timeToDeviceCleanup: number // in seconds + timeToDeviceCleanup: number // in minutes + deviceCleanupInterval: number // in minutes endpoints: { sub: string[] push: string[] @@ -105,12 +106,12 @@ export default (async(options: Options) => { log.info('deviceCleanerLoop enabled') // This functionality is implemented in the Reaper unit because this unit cannot be replicated - const deviceCleanerLoop = async() => { + const deviceCleanerLoop = () => setTimeout(async() => { + log.info('Checking dead devices [interval: %s]', options.deviceCleanupInterval) try { - await new Promise(r => setTimeout(r, 120_000)) // 2 min delay const absenceDuration = options.timeToDeviceCleanup const {deadDevices} = await runTransactionDev(wireutil.global, GetDeadDevices, { - time: options.timeToDeviceCleanup * 1000 + time: options.timeToDeviceCleanup * 60 * 1000 }, {sub, push, router}) for (const {serial, present} of deadDevices) { @@ -122,11 +123,9 @@ export default (async(options: Options) => { 'Removing a dead device [serial: %s, absence_duration: %.1f %s]', serial, ... ( - absenceDuration >= 3600 // if more 1 hour - ? [absenceDuration / 3600, 'hrs'] - : absenceDuration >= 60 // if more 1 minute - ? [absenceDuration / 60, 'min'] - : [absenceDuration, 'sec'] + absenceDuration >= 60 // if more 1 hour + ? [absenceDuration / 60, 'hrs'] + : [absenceDuration, 'min'] ) ) @@ -138,29 +137,38 @@ export default (async(options: Options) => { } catch (err: any) { log.error('Dead device check failed with error: %s', err?.message) } finally { - return deviceCleanerLoop() + deviceCleanerLoop() } - } + }, options.deviceCleanupInterval * 60 * 1000) deviceCleanerLoop() } - try { - log.info('Reaping devices with no heartbeat') + const init = async() => { + try { + log.info('Reaping devices with no heartbeat') - // Listen to changes - sub.on('message', router.handler()) + // Listen to changes + sub.on('message', router.handler()) - // Load initial state - const {devices} = await runTransactionDev(wireutil.global, GetPresentDevices, {}, {sub, push, router}) + // Load initial state + const {devices} = await runTransactionDev(wireutil.global, GetPresentDevices, {}, {sub, push, router}) - const now = Date.now() - devices.forEach((serial: string) => { - ttlset.bump(serial, now, TTLSet.SILENT) - }) - } - catch (err: any) { - log.fatal('Unable to load initial state: %s', err?.message) - lifecycle.fatal() + const now = Date.now() + devices?.forEach((serial: string) => { + ttlset.bump(serial, now, TTLSet.SILENT) + }) + } + catch (err: any) { + if (err?.message === 'Timeout when running transaction') { + log.error('Load initial state error: Timeout when running transaction, retry') + setTimeout(init, 2000) + return + } + log.fatal('Unable to load initial state: %s', err?.message) + lifecycle.fatal() + } } + + init() }) From 587bd19facc8cadd477c720104baa1086c26fdf0 Mon Sep 17 00:00:00 2001 From: "e.khalilov" Date: Mon, 6 Oct 2025 14:08:45 +0300 Subject: [PATCH 3/4] revert api edits --- lib/units/api/controllers/devices.js | 195 ++++++++++++------ lib/units/api/paths/devices/groups/{id}.js | 6 +- .../api/paths/devices/{serial}/groups/{id}.js | 6 +- lib/units/api/paths/groups/{id}/teams.js | 3 +- lib/units/api/paths/team.js | 3 +- lib/units/api/paths/team/{id}.js | 3 +- lib/units/api/paths/team/{id}/delete.js | 3 +- .../api/paths/team/{id}/group/{group}.js | 3 +- lib/units/api/paths/team/{id}/user/{email}.js | 3 +- lib/units/api/paths/teams.js | 3 +- lib/units/api/paths/user/{email}/teams.js | 3 +- lib/units/api/swagger/api_v1.yaml | 8 +- 12 files changed, 153 insertions(+), 86 deletions(-) diff --git a/lib/units/api/controllers/devices.js b/lib/units/api/controllers/devices.js index 41b55eb21b..7d9f916420 100644 --- a/lib/units/api/controllers/devices.js +++ b/lib/units/api/controllers/devices.js @@ -4,12 +4,19 @@ import dbapi from '../../../db/api.js' import logger from '../../../util/logger.js' import * as apiutil from '../../../util/apiutil.js' import * as lockutil from '../../../util/lockutil.js' +import util from 'util' import {v4 as uuidv4} from 'uuid' +import wire from '../../../wire/index.js' +import wireutil from '../../../wire/util.js' +import {WireRouter} from '../../../wire/router.js' import * as fake from '../../../util/fakedevice.js' +import deviceutil from '../../../util/deviceutil.js' +import * as jwtutil from '../../../util/jwtutil.js' import useDevice, {UseDeviceError} from '../helpers/useDevice.js' import * as Sentry from '@sentry/node' import {accessTokenAuth} from '../helpers/securityHandlers.js' -const log = logger.createLogger('api:controllers:devices') +import {DeviceOriginGroupMessage} from '../../../wire/wire.js' +var log = logger.createLogger('api:controllers:devices') /* ------------------------------------ PRIVATE FUNCTIONS ------------------------------- */ function filterGenericDevices(req, res, devices) { @@ -336,77 +343,141 @@ function getDeviceBookings(req, res) { apiutil.internalError(res, 'Failed to get device bookings: ', err.stack) }) } +function addOriginGroupDevices(req, res) { + const serials = apiutil.getBodyParameter(req.body, 'serials') + const fields = apiutil.getQueryParameter(req.query.fields) + const target = apiutil.getQueryParameter(req.query.redirected) ? 'device' : 'devices' -// Note: Device lists in groups change automatically -const addDeviceToOriginGroup = async(req, res) => { - try { - const serial = req.query.serial || req.params.serial - const fields = req.query.fields + async function askUpdateDeviceOriginGroup(group, serial) { + log.info('[addOriginGroupDevices] Ask to update device origin group [serial: "%s", group: "%s"]', serial, group.id) + const signature = util.format('%s', uuidv4()).replace(/-/g, '') + const result = await dbapi.updateDeviceOriginGroup(serial, group, signature) - if (!serial?.length) { - throw new Error('serial is not specified') - } + if (result) { + log.info('[addOriginGroupDevices] Successfully updated device origin group [serial: "%s", group: "%s"]', serial, group.id) - const targetGroup = await dbapi.getUserGroup(req.user.email, req.params.id) - if (!targetGroup) { - throw new Error('target group not found') - } + const device = await dbapi.loadDeviceBySerial(serial) + if (fields) { + return _.pick(apiutil.publishDevice(device, req), fields.split(',')) + } - const stats = await dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial) - if (stats.modifiedCount === 0) { - return apiutil.lightComputeStats(res, stats) + return apiutil.publishDevice(device, req) } - - const signature = uuidv4().replace(/-/g, '') - await dbapi.updateDeviceOriginGroup(serial, targetGroup, signature) - - lockutil.unlockDevice({device: stats.changes[0].new_val}) - - const device = await dbapi.loadDeviceBySerial(serial) - return apiutil.respond(res, 200, 'Updated (device)', apiutil.publishDevice(device, req)) } - catch (/** @type {any} */e) { - log.error('[addDeviceToOriginGroup] failed to update device origin group: %s', e?.message) - apiutil.internalError('Failed to update device origin group') + + function updateDeviceOriginGroup(group, serial) { + log.info('[addOriginGroupDevices] Add device %s to origin group %s', serial, group.id) + const lock = {} + return dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial).then(function(stats) { + if (stats.modifiedCount === 0) { + return apiutil.lightComputeStats(res, stats) + } + lock.device = stats.changes[0].new_val + return dbapi.isUpdateDeviceOriginGroupAllowed(serial, group) + .then(function(updatingAllowed) { + if (!updatingAllowed) { + log.error('[addOriginGroupDevices] Device group editing is not allowed %s', serial, group.id) + apiutil.respond(res, 403, 'Forbidden (device is currently booked)') + return Promise.reject('booked') + } + return askUpdateDeviceOriginGroup(group, serial) + }) + }) + .finally(function() { + lockutil.unlockDevice(lock) + }) } -} + function updateDevicesOriginGroup(group, serials) { + let results = [] + return Promise.each(serials, function(serial) { + return updateDeviceOriginGroup(group, serial).then(function(result) { + results.push(result) + }) + }) + .then(function() { -// Note: Device lists in groups change automatically -const removeDeviceFromOriginGroup = async(req, res) => { - try { - const serial = req.query.serial - const fields = req.query.fields + /** @type {any} */ + const result = target === 'device' ? {device: {}} : {devices: []} + results = _.without(results, 'unchanged') + if (!results.length) { + return apiutil.respond(res, 200, `Unchanged (${target})`, result) + } + results = _.without(results, 'not found') + if (!results.length) { + return apiutil.respond(res, 404, `Not Found (${target})`) + } + if (target === 'device') { + result.device = results[0] + } + else { + result.devices = results + } + return apiutil.respond(res, 200, `Updated (${target})`, result) + }) + .catch(function(err) { + if (err !== 'booked' && err !== 'timeout' && err !== 'busy') { + throw err + } + }) + } - if (!serial?.length) { - throw new Error('serial is not specified') + return dbapi.getUserGroup(req.user.email, req.params.id).then(function(group) { + log.info('[addOriginGroupDevices] Add devices to origin group with id: %s [%s | %s]', group?.id, group?.name, group?.class) + if (!group) { + return false } - const targetGroup = await dbapi.getRootGroup() - if (!targetGroup) { - throw new Error('target group not found') + if (!apiutil.isOriginGroup(group.class)) { + return apiutil.respond(res, 400, 'Bad Request (this group cannot act as an origin one)') } - - const lock = {} - const stats = await dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial) - if (stats.modifiedCount === 0) { - return apiutil.lightComputeStats(res, stats) + if (typeof serials !== 'undefined') { + return updateDevicesOriginGroup(group, _.without(serials.split(','), '').filter(function(serial) { + return group.devices.indexOf(serial) < 0 + })) } - lock.device = stats.changes[0].new_val - const signature = uuidv4().replace(/-/g, '') - await dbapi.updateDeviceOriginGroup(serial, targetGroup, signature) - - lockutil.unlockDevice(lock) - - const device = await dbapi.loadDeviceBySerial(serial) - return apiutil.respond(res, 200, 'Updated (device)', apiutil.publishDevice(device, req)) - } - catch (/** @type {any} */e) { - log.error('[addDeviceToOriginGroup] failed to update device origin group: %s', e?.message) - apiutil.internalError('Failed to update device origin group') - } + log.info('[addOriginGroupDevices] Serials list is not specified') + return dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) { + if (group.class === apiutil.BOOKABLE) { + return devices + } + return extractStandardizableDevices(devices) + }) + .then(function(devices) { + const serials = [] + devices.forEach(function(device) { + if (group.devices.indexOf(device.serial) < 0) { + serials.push(device.serial) + } + }) + return updateDevicesOriginGroup(group, serials) + }) + }) + .catch(function(err) { + apiutil.internalError(res, `Failed to update ${target} origin group: `, err.stack) + }) +} +function addOriginGroupDevice(req, res) { + apiutil.redirectApiWrapper('serial', addOriginGroupDevices, req, res) } +function removeOriginGroupDevices(req, res) { + return dbapi.getUserGroup(req.user.email, req.params.id).then(function(group) { + if (!group) { + return false + } + if (!apiutil.checkBodyParameter(req.body, 'serials')) { + req.body = {serials: group.devices.join()} + } + return dbapi.getRootGroup().then(function(group) { + req.params.id = group?.id + return addOriginGroupDevices(req, res) + }) + }) +} +function removeOriginGroupDevice(req, res) { + apiutil.redirectApiWrapper('serial', removeOriginGroupDevices, req, res) +} function putDeviceInfoBySerial(req, res) { const serial = req.params.serial const body = req.body @@ -623,8 +694,10 @@ export {getDeviceOwner} export {getDeviceGroups} export {getDeviceType} export {getDeviceBookings} -export {addDeviceToOriginGroup} -export {removeDeviceFromOriginGroup} +export {addOriginGroupDevice} +export {addOriginGroupDevices} +export {removeOriginGroupDevice} +export {removeOriginGroupDevices} export {deleteDevice} export {deleteDevices} export {updateStorageInfo} @@ -641,8 +714,10 @@ export default { getDeviceGroups: getDeviceGroups, getDeviceType: getDeviceType, getDeviceBookings: getDeviceBookings, - addDeviceToOriginGroup: addDeviceToOriginGroup, - removeDeviceFromOriginGroup: removeDeviceFromOriginGroup, + addOriginGroupDevice: addOriginGroupDevice, + addOriginGroupDevices: addOriginGroupDevices, + removeOriginGroupDevice: removeOriginGroupDevice, + removeOriginGroupDevices: removeOriginGroupDevices, deleteDevice: deleteDevice, deleteDevices: deleteDevices, updateStorageInfo: updateStorageInfo, diff --git a/lib/units/api/paths/devices/groups/{id}.js b/lib/units/api/paths/devices/groups/{id}.js index aae5e15e46..584cc9ba37 100644 --- a/lib/units/api/paths/devices/groups/{id}.js +++ b/lib/units/api/paths/devices/groups/{id}.js @@ -1,15 +1,15 @@ // Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY // Generated for controller devices -import {addDeviceToOriginGroup, removeDeviceFromOriginGroup} from '../../../controllers/devices.js' +import {addOriginGroupDevices, removeOriginGroupDevices} from '../../../controllers/devices.js' export function put(req, res) { - return addDeviceToOriginGroup(req, res) + return addOriginGroupDevices(req, res) } export function del(req, res) { - return removeDeviceFromOriginGroup(req, res) + return removeOriginGroupDevices(req, res) } diff --git a/lib/units/api/paths/devices/{serial}/groups/{id}.js b/lib/units/api/paths/devices/{serial}/groups/{id}.js index f77a44014c..8c9cd0c583 100644 --- a/lib/units/api/paths/devices/{serial}/groups/{id}.js +++ b/lib/units/api/paths/devices/{serial}/groups/{id}.js @@ -1,15 +1,15 @@ // Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY // Generated for controller devices -import {addDeviceToOriginGroup, removeDeviceFromOriginGroup} from '../../../../controllers/devices.js' +import {addOriginGroupDevice, removeOriginGroupDevice} from '../../../../controllers/devices.js' export function put(req, res) { - return addDeviceToOriginGroup(req, res) + return addOriginGroupDevice(req, res) } export function del(req, res) { - return removeDeviceFromOriginGroup(req, res) + return removeOriginGroupDevice(req, res) } diff --git a/lib/units/api/paths/groups/{id}/teams.js b/lib/units/api/paths/groups/{id}/teams.js index e6dd871d80..ec99becf57 100644 --- a/lib/units/api/paths/groups/{id}/teams.js +++ b/lib/units/api/paths/groups/{id}/teams.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {getGroupsTeams} from '../../../controllers/teams.js' diff --git a/lib/units/api/paths/team.js b/lib/units/api/paths/team.js index 31d94d524b..d8312985e7 100644 --- a/lib/units/api/paths/team.js +++ b/lib/units/api/paths/team.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {createTeam} from '../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}.js b/lib/units/api/paths/team/{id}.js index dc185b3d5b..0227d69241 100644 --- a/lib/units/api/paths/team/{id}.js +++ b/lib/units/api/paths/team/{id}.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {getTeamById, updateTeam, removeFromTeam} from '../../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}/delete.js b/lib/units/api/paths/team/{id}/delete.js index 48da77cdec..b2c1ca799a 100644 --- a/lib/units/api/paths/team/{id}/delete.js +++ b/lib/units/api/paths/team/{id}/delete.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {deleteTeam} from '../../../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}/group/{group}.js b/lib/units/api/paths/team/{id}/group/{group}.js index 63038806a8..1784fe3537 100644 --- a/lib/units/api/paths/team/{id}/group/{group}.js +++ b/lib/units/api/paths/team/{id}/group/{group}.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {removeGroupFromTeam} from '../../../../controllers/teams.js' diff --git a/lib/units/api/paths/team/{id}/user/{email}.js b/lib/units/api/paths/team/{id}/user/{email}.js index 53120ab094..9b60901df0 100644 --- a/lib/units/api/paths/team/{id}/user/{email}.js +++ b/lib/units/api/paths/team/{id}/user/{email}.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {removeUserFromTeam} from '../../../../controllers/teams.js' diff --git a/lib/units/api/paths/teams.js b/lib/units/api/paths/teams.js index 953c2467c9..1b7a3cacf1 100644 --- a/lib/units/api/paths/teams.js +++ b/lib/units/api/paths/teams.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {getTeams} from '../controllers/teams.js' diff --git a/lib/units/api/paths/user/{email}/teams.js b/lib/units/api/paths/user/{email}/teams.js index 537b227911..6e8dc21786 100644 --- a/lib/units/api/paths/user/{email}/teams.js +++ b/lib/units/api/paths/user/{email}/teams.js @@ -1,5 +1,4 @@ -// Generated by /lib/units/api/gen_routes.py. DO NOT EDIT MANUALLY -// Generated for controller teams +// teams import {getUserTeams} from '../../../controllers/teams.js' diff --git a/lib/units/api/swagger/api_v1.yaml b/lib/units/api/swagger/api_v1.yaml index 074cf880de..1f6f2ed149 100644 --- a/lib/units/api/swagger/api_v1.yaml +++ b/lib/units/api/swagger/api_v1.yaml @@ -2759,7 +2759,7 @@ paths: summary: Adds devices into an origin group description: Adds devices into an origin group along with updating each added device; returns the updated devices - operationId: addDeviceToOriginGroup + operationId: addOriginGroupDevices parameters: - name: id in: path @@ -2815,7 +2815,7 @@ paths: summary: Removes devices from an origin group description: Removes devices from an origin group along with updating each removed device; returns the updated devices - operationId: removeDeviceFromOriginGroup + operationId: removeOriginGroupDevices parameters: - name: id in: path @@ -3045,7 +3045,7 @@ paths: summary: Adds a device into an origin group description: Adds a device into an origin group along with updating the added device; returns the updated device - operationId: addDeviceToOriginGroup + operationId: addOriginGroupDevice parameters: - name: serial in: path @@ -3086,7 +3086,7 @@ paths: summary: Removes a device from an origin group description: Removes a device from an origin group along with updating the removed device; returns the updated device - operationId: removeDeviceFromOriginGroup + operationId: removeOriginGroupDevice parameters: - name: serial in: path From c937b37ce32fdedf6c05c93e0da68e054cf43a05 Mon Sep 17 00:00:00 2001 From: "e.khalilov" Date: Mon, 6 Oct 2025 14:10:27 +0300 Subject: [PATCH 4/4] revert ui (types) edits --- .../types/addDeviceToOriginGroupParams.ts | 14 -------------- ui/src/generated/types/index.ts | 2 -- .../types/removeDeviceFromOriginGroupParams.ts | 14 -------------- 3 files changed, 30 deletions(-) delete mode 100644 ui/src/generated/types/addDeviceToOriginGroupParams.ts delete mode 100644 ui/src/generated/types/removeDeviceFromOriginGroupParams.ts diff --git a/ui/src/generated/types/addDeviceToOriginGroupParams.ts b/ui/src/generated/types/addDeviceToOriginGroupParams.ts deleted file mode 100644 index 3219ad7eea..0000000000 --- a/ui/src/generated/types/addDeviceToOriginGroupParams.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Generated by orval v7.3.0 🍺 - * Do not edit manually. - * DeviceHub - * Control and manages real Smartphone devices from browser and restful apis - * OpenAPI spec version: 1.4.7 - */ - -export type AddDeviceToOriginGroupParams = { - /** - * Comma-seperated list of fields; only listed fields will be returned in response - */ - fields?: string -} diff --git a/ui/src/generated/types/index.ts b/ui/src/generated/types/index.ts index 5ffdf6dce2..dd47e42104 100644 --- a/ui/src/generated/types/index.ts +++ b/ui/src/generated/types/index.ts @@ -12,7 +12,6 @@ export * from './adbKeyAddedResponse' export * from './adbPortResponse' export * from './adbRangeResponse' export * from './addAdbPublicKeyBody' -export * from './addDeviceToOriginGroupParams' export * from './addDevicesParams' export * from './addOriginGroupDevicesParams' export * from './addUserDevicePayload' @@ -109,7 +108,6 @@ export * from './groupState' export * from './groupsPayload' export * from './ownerResponse' export * from './remoteConnectUserDeviceResponse' -export * from './removeDeviceFromOriginGroupParams' export * from './removeOriginGroupDevicesParams' export * from './revokeAdmin200' export * from './serviceUserResponse' diff --git a/ui/src/generated/types/removeDeviceFromOriginGroupParams.ts b/ui/src/generated/types/removeDeviceFromOriginGroupParams.ts deleted file mode 100644 index eea3e0ec99..0000000000 --- a/ui/src/generated/types/removeDeviceFromOriginGroupParams.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Generated by orval v7.3.0 🍺 - * Do not edit manually. - * DeviceHub - * Control and manages real Smartphone devices from browser and restful apis - * OpenAPI spec version: 1.4.7 - */ - -export type RemoveDeviceFromOriginGroupParams = { - /** - * Comma-seperated list of fields; only listed fields will be returned in response - */ - fields?: string -}