fix(web): error/404 экраны, сплит бандла и bulk-мутации
- RouteErrorComponent/RouteNotFoundComponent на root-маршруте (RU-копирайт, retry + переход на главную; redirect в портал не мигает ошибкой) - settings: форма инициализируется один раз — фоновый refetch больше не затирает ввод пользователя - code splitting: autoCodeSplitting роутов + manualChunks (react/router/query/ charts/dnd); вход ~507KB вместо единого чанка 1.57MB, recharts (330KB) грузится лениво; версия recharts в packages/ui выровнена с apps/web (3.8.0) - bulk-эндпоинты: POST /agents/approve-bulk и PUT /policy-sets/:id/agents — назначение набора агентам одним запросом вместо N×(GET+PUT) - оптимистичные обновления с rollback: approve, approve-bulk, удаление агента, переключение набора - тесты bulk-операций (45 passed)
This commit is contained in:
@@ -3,6 +3,7 @@ import { repos } from '@evofw/db'
|
||||
import {
|
||||
createPolicySetBodySchema,
|
||||
patchPolicySetBodySchema,
|
||||
agentIdsBodySchema,
|
||||
} from '@evofw/shared'
|
||||
import { AppError } from '../plugins/error-handler.js'
|
||||
import type { AppConfig } from '../config.js'
|
||||
@@ -100,4 +101,59 @@ export const policySetsRoutes: FastifyPluginAsync<{ config: AppConfig }> = async
|
||||
}
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
/**
|
||||
* Replace which agents have this set assigned: listed agents gain the set
|
||||
* (other assignments preserved), unlisted agents lose it.
|
||||
*/
|
||||
app.put<{ Params: { id: string } }>(
|
||||
'/policy-sets/:id/agents',
|
||||
async (req) => {
|
||||
const set = repos.getPolicySet(app.db, req.params.id)
|
||||
if (!set) throw new AppError('NOT_FOUND', 'Policy set not found', 404)
|
||||
const body = agentIdsBodySchema.parse(req.body)
|
||||
|
||||
const target = new Set(body.agent_ids)
|
||||
for (const agentId of body.agent_ids) {
|
||||
if (!repos.getAgent(app.db, agentId)) {
|
||||
throw new AppError('NOT_FOUND', `Agent not found: ${agentId}`, 404)
|
||||
}
|
||||
}
|
||||
|
||||
const current = repos.listAgentIdsForSet(app.db, set.id)
|
||||
const toAdd = body.agent_ids.filter((id) => !current.includes(id))
|
||||
const toRemove = current.filter((id) => !target.has(id))
|
||||
|
||||
const applyAssignment = (agentId: string, withSet: boolean) => {
|
||||
const others = repos
|
||||
.listSetsForAgent(app.db, agentId)
|
||||
.map((s) => s.setId)
|
||||
.filter((id) => id !== set.id)
|
||||
const next = withSet ? [...others, set.id] : others
|
||||
repos.setAgentPolicySets(app.db, agentId, next)
|
||||
}
|
||||
|
||||
app.sqlite.transaction(() => {
|
||||
for (const agentId of toAdd) applyAssignment(agentId, true)
|
||||
for (const agentId of toRemove) applyAssignment(agentId, false)
|
||||
})()
|
||||
|
||||
auditMutation(app, config, req, {
|
||||
action: 'policy_set.agents.update',
|
||||
targetType: 'app_resource',
|
||||
targetId: set.id,
|
||||
summary: `Назначение набора ${set.name} обновлено`,
|
||||
details: {
|
||||
set_id: set.id,
|
||||
added: toAdd,
|
||||
removed: toRemove,
|
||||
},
|
||||
})
|
||||
return {
|
||||
agent_ids: repos.listAgentIdsForSet(app.db, set.id),
|
||||
added: toAdd,
|
||||
removed: toRemove,
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user