fix(terminal): запретить выключенные устройства и исправить средний RTT
This commit is contained in:
1 parent
5b3133ce73
commit
8b7bff165a
18 files changed
+647
-7
No files matched your search
@@ -4,11 +4,11 @@ import {validatorCompiler,serializerCompiler} from "@fastify/type-provider-zod"
|
||||
import {createExecRoutes,type ExecDependencies} from "./exec.js"
|
||||
import type {servers} from "../db/schema.js"
|
||||
const outputs:Record<string,unknown>={getIdentity:{name:"test"},getResource:{}}
|
||||
const calls:unknown[][]=[];let exists=true,lookups=0
|
||||
const calls:unknown[][]=[];let exists=true,lookups=0,enabled=true,clients=0
|
||||
const names=["getIpAddresses","getInterfaces","getResource","getIdentity","getIpRoutes","getFirewallFilters","getBgpSessions","getOspfNeighbors","getOspfAreas","getOspfInstances","getOspfInterfaceTemplates","getBfdSessions","getLogs","ping"]
|
||||
const client=Object.fromEntries(names.map(key=>[key,async(...args:unknown[])=>{calls.push([key,...args]);if(outputs[key] instanceof Error)throw outputs[key];return outputs[key]??[]}])) as unknown as ReturnType<ExecDependencies["createClient"]>
|
||||
const app=Fastify();app.setValidatorCompiler(validatorCompiler);app.setSerializerCompiler(serializerCompiler)
|
||||
await app.register(createExecRoutes({getServer:async()=>{lookups++;return exists?{id:1,name:"Тест",host:"192.0.2.1",enabled:true} as typeof servers.$inferSelect:undefined},createClient:()=>client}),{prefix:"/api"})
|
||||
await app.register(createExecRoutes({getServer:async()=>{lookups++;return exists?{id:1,name:"Тест",host:"192.0.2.1",enabled} as typeof servers.$inferSelect:undefined},createClient:()=>{clients++;return client}}),{prefix:"/api"})
|
||||
const send=(command:string)=>app.inject({method:"POST",url:"/api/servers/1/exec",payload:{command}})
|
||||
for(const command of ["ip address print","interface print","system resource print","system identity print","ip route print","ip firewall filter print","routing bgp session print","routing ospf neighbor print","routing ospf area print","routing ospf instance print","routing ospf interface print","routing bfd session print","log print","/IP ADDRESS PRINT","help"]){
|
||||
const response=await send(command);assert.equal(response.statusCode,200,command);assert.equal(typeof response.json().output,"string")
|
||||
@@ -23,9 +23,15 @@ outputs.getOspfInstances=[{name:"test"}];assert.match((await send("routing ospf
|
||||
outputs.getFirewallFilters=[{".id":"*1",chain:"input",action:"accept",packets:"0"}];assert.ok((await send("ip firewall filter print")).json().output.includes("packets=0 bytes=?"))
|
||||
outputs.getResource={"free-memory":"junk"};assert.ok(!(await send("system resource print")).json().output.includes("NaN"))
|
||||
outputs.ping=[{seq:"0",status:"timeout"},{sent:"0",received:"0"}];const ping=(await send("ping 192.0.2.2 count=2")).json().output;assert.ok(ping.includes("seq=0 timeout"));assert.ok(ping.includes("sent=0 received=0 packet-loss=?"));assert.deepEqual(calls.at(-1),["ping","192.0.2.2",2])
|
||||
outputs.ping=[{seq:"0",time:"1ms"},{seq:"1",time:"9ms",sent:"2",received:"2","avg-rtt":"5ms"}];assert.match((await send("ping 192.0.2.2 count=2")).json().output,/avg-rtt=5ms/)
|
||||
outputs.ping=[{seq:"0",time:"9ms",sent:"1",received:"1"}];assert.match((await send("ping 192.0.2.2 count=1")).json().output,/avg-rtt=\?/)
|
||||
for(const average of ["junk","",undefined]){outputs.ping=[{sent:"1",received:"1",time:"9ms","avg-rtt":average}];assert.match((await send("ping 192.0.2.2 count=1")).json().output,/avg-rtt=\?/)}
|
||||
outputs.ping=[{sent:"1",received:"0","avg-rtt":"0ms"}];assert.match((await send("ping 192.0.2.2 count=1")).json().output,/avg-rtt=\?/)
|
||||
outputs.ping=[{sent:"1",received:"1","avg-rtt":"0ms"}];assert.match((await send("ping 192.0.2.2 count=1")).json().output,/avg-rtt=0ms/)
|
||||
assert.equal((await send("ping 192.0.2.2 count=99")).statusCode,200);assert.deepEqual(calls.at(-1),["ping","192.0.2.2",20])
|
||||
assert.equal((await send("log print limit=500")).statusCode,200);assert.deepEqual(calls.at(-1),["getLogs",200])
|
||||
for(const command of ["system reboot","pingbad 192.0.2.2","log printable","ping","ping 192.0.2.2 count=0","ping 192.0.2.2 count=1junk","ping 192.0.2.2 count=1 count=2","log print limit=0","log print limit=3junk"]){const before=calls.length;assert.equal((await send(command)).statusCode,400,command);assert.equal(calls.length,before)}
|
||||
exists=false;assert.equal((await send("interface print")).statusCode,404);exists=true
|
||||
enabled=false;const beforeDisabled={clients,calls:calls.length};for(const command of["interface print","ping 192.0.2.2 count=1","help"])assert.equal((await send(command)).statusCode,409);assert.deepEqual({clients,calls:calls.length},beforeDisabled);enabled=true
|
||||
const before=lookups;for(const payload of [{},{command:""},{command:"x".repeat(1025)}])assert.equal((await app.inject({method:"POST",url:"/api/servers/1/exec",payload})).statusCode,400);assert.equal(lookups,before)
|
||||
await app.close();console.log("Exec HTTP: readonly allowlist, errors/known empty, unknown versus defaults, bounded arguments and missing/bad requests; no DB/RouterOS")
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod"
|
||||
import { db } from "../db/index.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
import { MikrotikClient } from "../services/mikrotik.js"
|
||||
import { observedDurationMs, observedInteger } from "../services/ospf-observed-values.js"
|
||||
import { ServerIdParamSchema, type ServerIdParams } from "../types/server.js"
|
||||
import type {
|
||||
RosIpAddress, RosInterface, RosResource, RosIdentity, RosIpRoute,
|
||||
@@ -208,7 +209,9 @@ function fmtPing(results: RosPingResult[], host: string): string {
|
||||
if (sum) {
|
||||
const loss = sum["packet-loss"] ?? "?"
|
||||
lines.push(` sent=${sum.sent} received=${sum.received??"?"} packet-loss=${loss}`)
|
||||
if (sum.time) lines.push(` avg-rtt=${sum.time}`)
|
||||
const average = sum["avg-rtt"]
|
||||
const received = typeof sum.received === "string" ? observedInteger(sum.received, 1) : null
|
||||
lines.push(` avg-rtt=${received !== null && typeof average === "string" && observedDurationMs(average) !== null ? average : "?"}`)
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
@@ -316,6 +319,7 @@ export function createExecRoutes(dependencies:ExecDependencies=defaults):Fastify
|
||||
app.post("/servers/:id/exec",{schema:{params:ServerIdParamSchema,body:ExecBodySchema}},async(req,reply)=>{
|
||||
const server=await dependencies.getServer((req.params as ServerIdParams).id)
|
||||
if(!server)return reply.status(404).send({error:"Устройство не найдено"})
|
||||
if(!server.enabled)return reply.status(409).send({error:"Устройство отключено: выполнение команд недоступно"})
|
||||
try {
|
||||
const output=await dispatch(dependencies.createClient(server),req.body.command)
|
||||
return reply.send({output})
|
||||
|
||||
@@ -397,6 +397,7 @@ export interface RosPingResult {
|
||||
"sent"?: string
|
||||
"received"?: string
|
||||
"packet-loss"?: string
|
||||
"avg-rtt"?: string
|
||||
"status"?: string // "timeout" when lost
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user