feat(api, web): enhance MikroTik integration and IP hit tracking
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m54s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

- Updated the `evofw-firewall.sh` script to improve the handling of NFT sets, ensuring compatibility with kernel limitations on counters and enhancing logging for better diagnostics.
- Introduced a new presence mode for MikroTik, allowing for real-time tracking of IP hits with updated last seen timestamps and packet counts.
- Enhanced the API to support the new presence mode, updating the database interactions to reflect the changes in how IP hits are recorded.
- Updated the agent detail view to display sync windows for MikroTik, providing clearer insights into blocked IPs and their activity.
- Improved documentation to reflect the new features and changes in the MikroTik handling process, ensuring clarity for users and developers.

These changes significantly enhance the monitoring capabilities and user experience for agents, particularly those using MikroTik devices.
This commit is contained in:
Denozordec
2026-08-07 14:46:06 +07:00
parent 4ee78032c4
commit ee3429b747
8 changed files with 237 additions and 54 deletions
@@ -185,4 +185,93 @@ describe('apply-report ip_hits / blocked-ips', () => {
})
expect(report.statusCode).toBeGreaterThanOrEqual(400)
})
it('mikrotik presence mode increments packets and refreshes last_seen', async () => {
const app = await appPromise
await app.ready()
const created = await app.inject({
method: 'POST',
url: '/api/v1/install-links',
payload: { name: 'mt-hits', platform: 'mikrotik' },
})
const link = created.json() as { id: string; agent_id: string }
const token = 'evofw_mt_hits_token_abcdefghijk'
await app.inject({
method: 'POST',
url: '/v1/agent/enroll',
headers: {
'content-type': 'application/json',
'x-evofw-seed': 'test-seed',
},
payload: {
name: 'mt-hits',
platform: 'mikrotik',
token,
install_link_id: link.id,
},
})
await app.inject({
method: 'POST',
url: `/api/v1/agents/${link.agent_id}/approve`,
})
const report1 = await app.inject({
method: 'POST',
url: '/v1/agent/apply-report',
headers: {
authorization: `Bearer ${token}`,
'content-type': 'application/json',
},
payload: {
status: 'ok',
packets_dropped: 3,
kernel_method: 'address-list',
source: 'mikrotik',
ip_hits: [{ ip: '203.0.113.50', packets: 1 }],
},
})
expect(report1.statusCode).toBe(200)
const list1 = await app.inject({
method: 'GET',
url: `/api/v1/agents/${link.agent_id}/blocked-ips`,
})
const body1 = list1.json() as {
items: { ip: string; packets: number; last_seen_at: string }[]
}
expect(body1.items).toHaveLength(1)
expect(body1.items[0]?.packets).toBe(1)
const firstSeen = body1.items[0]!.last_seen_at
await new Promise((r) => setTimeout(r, 5))
const report2 = await app.inject({
method: 'POST',
url: '/v1/agent/apply-report',
headers: {
authorization: `Bearer ${token}`,
'content-type': 'application/json',
},
payload: {
status: 'ok',
packets_dropped: 5,
kernel_method: 'address-list',
source: 'mikrotik',
ip_hits: [{ ip: '203.0.113.50', packets: 1 }],
},
})
expect(report2.statusCode).toBe(200)
const list2 = await app.inject({
method: 'GET',
url: `/api/v1/agents/${link.agent_id}/blocked-ips`,
})
const body2 = list2.json() as {
items: { ip: string; packets: number; last_seen_at: string }[]
}
expect(body2.items[0]?.packets).toBe(2)
expect(body2.items[0]!.last_seen_at >= firstSeen).toBe(true)
})
})