Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
7198ef8
Feature - Site status live api (#4)
paragmore Feb 17, 2026
baa5c51
feat: add API v2 endpoints (finance, pools, pool-stats, pool-manager)…
mukama Feb 17, 2026
4d777f1
fix: Improves user and roles ux (#12)
mukama Feb 19, 2026
a1ed2b7
feat: add GET /auth/finance/subsidy-fees endpoint (#14)
mukama Feb 20, 2026
eb35160
Adding CI (#23)
andretetherio Feb 26, 2026
8e003f0
feat: add alerts API v2 endpoints (#24)
mukama Mar 3, 2026
d02a78c
feat: add metrics API v2 endpoints (#21)
mukama Mar 5, 2026
c454b8a
feat: add finance revenue and revenue-summary endpoints (#22)
mukama Mar 5, 2026
cd01902
feat: add device listing API v2 endpoints (#25)
mukama Mar 6, 2026
4925c0b
Feat/add get configs route (#26)
paragmore Mar 9, 2026
b712aa8
Remove old pool manager apis (#29)
paragmore Mar 9, 2026
e47b962
feat: add GET /auth/finance/hash-revenue endpoint (#27)
mukama Mar 9, 2026
24372e3
non rpc method calls to ork (#28)
tekwani Mar 10, 2026
da73911
fix: address OWASP security review findings for v2 API endpoints (#30)
mukama Mar 11, 2026
cb37c12
ci: enforce coverage (#31)
andretetherio Mar 12, 2026
28d479e
feat: pool config stats (#32)
tekwani Mar 12, 2026
f7b4e1c
feat: add GET /auth/miners with getThingsCount (#33)
mukama Mar 14, 2026
5269caf
fix: config routes read permissions fix (#34)
tekwani Mar 14, 2026
c15cc03
ci: improve coverage summary (NYC table) (#35)
andretetherio Mar 14, 2026
b82a4b4
feat: containers pool stats (#36)
tekwani Mar 17, 2026
4a9783b
fix: add schema bounds to prevent negative limit/offset bypass (#37)
mukama Mar 18, 2026
da73686
ci: move away from pull_request_target (#38)
andretetherio Mar 19, 2026
5f88cac
feat: add groups stats endpoint and racks filter for metrics (#47)
mukama Apr 9, 2026
c38b5c6
Add new auth/cooling system api (#51)
paragmore Apr 13, 2026
c841021
fix: sanitize queryActions input to prevent injection and DoS (#49)
mukama Apr 13, 2026
157f405
Add energy views endpoint support (#52)
paragmore Apr 14, 2026
284f63b
feat: microsoft oauth (#54)
tekwani Apr 16, 2026
b6f1076
Support site overview endpoints for dcs and miner group wise overview…
paragmore Apr 16, 2026
bb0dab9
Support new dcs device tags update and fix cooling system apis (#56)
paragmore Apr 20, 2026
f54cc68
Add hvac circuit 1 and layout api updates (#58)
paragmore Apr 21, 2026
2b66502
Add site effciency api (#57)
paragmore Apr 21, 2026
c2fc18f
Add explorer racks api (#59)
paragmore Apr 23, 2026
14656b4
(improvement) Explorer handlers, Racks list, Search filtering flow im…
boris91 Apr 24, 2026
0b6ffe1
feat: group hashrate by miner and container (#62)
tekwani Apr 28, 2026
b48c8e0
fix: finance v2 handler RPC regressions (#67)
mukama Apr 28, 2026
bbd641e
Fix memory leak in authCheck by implementing LRU caching and removing…
mukama Apr 28, 2026
79823db
Support summary for grouped by in hashrate metrics api (#68)
paragmore Apr 29, 2026
2a67b20
fix: groups stats accepts rack ids and returns rack objects (#69)
mukama Apr 29, 2026
52de78b
Add groupBy for metrics consumption api (#70)
paragmore Apr 29, 2026
fe78440
Update the cooling system handlers to fix flow readings (#71)
paragmore Apr 29, 2026
add4db7
fix(finance): correct energy-balance period aggregation (#72)
mukama May 1, 2026
aee490d
chore: facs version update (#73)
tekwani May 2, 2026
efd8d32
Update the dcs apis to support the new tags (#74)
paragmore May 3, 2026
ec96b53
develop sync (#76)
tekwani May 8, 2026
77c7f8e
feat: adopt JWT-based svc-facs-auth (#53)
mukama May 14, 2026
b0177fe
feat: forecast endpoints (#80)
tekwani May 18, 2026
a3dba5c
feat: add rack grouping to metrics/hashrate endpoint (#82)
mukama May 20, 2026
eabac28
feat: add rack grouping to metrics/consumption endpoint (#83)
mukama May 20, 2026
7970688
feat: work order + spare parts HTTP API (#79)
mukama May 22, 2026
1b7f7f5
(fix) NPM audit, Dependencies invulnerability fixed (#84)
boris91 May 25, 2026
6d1f48d
feat: list firmwares endpoint (#86)
tekwani Jun 1, 2026
50616a9
feat: miner log download via Hypercore P2P streaming (#85)
paragmore Jun 1, 2026
f31bc91
feat: forecast history endpoint (#87)
tekwani Jun 4, 2026
05aafdb
feat: forecast settings (#89)
tekwani Jun 4, 2026
8c60883
fix: add info params + fix WO auth (#88)
mukama Jun 4, 2026
2d81fdd
Chore/main sync (#91)
tekwani Jun 9, 2026
2f53d4e
fix: harden miner download-logs API and P2P log streaming (#94)
paragmore Jun 13, 2026
af9c55c
feat: align site/status/live with header UI logic (#93)
paragmore Jun 13, 2026
0da5886
feat: GET /site/power-consumption history API (#96)
paragmore Jun 15, 2026
457336d
Feat: Inventory v3 additions (#95)
mukama Jun 16, 2026
929f27c
feat(alerts): filter & search alerts by device tag; fix site deviceId…
paragmore Jun 16, 2026
6171ee1
Feat: batch create endpoints for work orders and spare parts (#98)
mukama Jun 18, 2026
4f5c0e8
feat(cooling): redesigned Operations Centre cooling/energy data (#100)
paragmore Jun 19, 2026
59f41e9
Merge branch 'develop' into sync/dev
tekwani Jun 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@ tests/integration/db/*
tests/integration/config/*
tests/integration/status/*
tests/integration/store/*
tests/e2e/db/*
tests/e2e/config/*
tests/e2e/status/*
tests/e2e/store/*
tests/e2e/tmp/*
coverage
.scannerwork
9 changes: 8 additions & 1 deletion tests/integration/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -901,20 +901,25 @@ test('Api', { timeout: 90000 }, async (main) => {
t.ok(data.hashrate.value !== undefined, 'hashrate should have value')
t.ok(data.hashrate.nominal !== undefined, 'hashrate should have nominal')
t.ok(data.hashrate.utilization !== undefined, 'hashrate should have utilization')
t.is(data.hashrate.unit, 'MH/s', 'hashrate unit should be MH/s')

// Verify power structure
t.ok(data.power.value !== undefined, 'power should have value')
t.ok(data.power.nominal !== undefined, 'power should have nominal')
t.ok(data.power.utilization !== undefined, 'power should have utilization')
t.is(data.power.unit, 'W', 'power unit should be W')
t.ok(data.power.alert !== undefined, 'power should have alert')
t.ok(typeof data.power.error === 'boolean', 'power should have boolean error')

// Verify efficiency structure
t.ok(data.efficiency.value !== undefined, 'efficiency should have value')
t.is(data.efficiency.unit, 'W/TH/s', 'efficiency unit should be W/TH/s')

// Verify miners structure
t.ok(data.miners.online !== undefined, 'miners should have online')
t.ok(data.miners.offline !== undefined, 'miners should have offline')
t.ok(data.miners.error !== undefined, 'miners should have error')
t.ok(data.miners.sleep !== undefined, 'miners should have sleep')
t.is(data.miners.sleep, undefined, 'miners should not have a derived sleep field')
t.ok(data.miners.total !== undefined, 'miners should have total')
t.ok(data.miners.containerCapacity !== undefined, 'miners should have containerCapacity')

Expand All @@ -926,6 +931,8 @@ test('Api', { timeout: 90000 }, async (main) => {

// Verify pools structure
t.ok(data.pools.totalHashrate !== undefined, 'pools should have totalHashrate')
t.ok(data.pools.totalHashrate.value !== undefined, 'pools totalHashrate should have value')
t.is(data.pools.totalHashrate.unit, 'MH/s', 'pools totalHashrate unit should be MH/s')
t.ok(data.pools.activeWorkers !== undefined, 'pools should have activeWorkers')
t.ok(data.pools.totalWorkers !== undefined, 'pools should have totalWorkers')

Expand Down
137 changes: 137 additions & 0 deletions tests/unit/handlers/alerts.handlers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,140 @@ test('getAlertsHistory - throws on invalid date range', async (t) => {
t.is(err.message, 'ERR_INVALID_DATE_RANGE', 'should throw date range error')
}
})

// ==================== device tag (message) — filter & search ====================

test('extractAlertsFromThings - preserves message (device tag)', (t) => {
const things = [
{
id: 'dcs-1',
type: 'dcs-siemens',
code: 'PCS7',
info: { container: 'cont-A' },
last: {
alerts: [
{ severity: 'warning', name: 'flow_warning', message: 'FIT-7513', description: 'Cooling-loop flow below warning threshold — FIT-7513: 320m³/h (threshold 330m³/h)' }
]
}
}
]

const result = extractAlertsFromThings(things)
t.is(result[0].message, 'FIT-7513', 'should preserve the device tag in message')
})

test('flattenHistoryAlert - preserves message (device tag)', (t) => {
const alert = {
name: 'flow_warning',
description: 'Cooling-loop flow below warning threshold — FIT-7513: 320m³/h (threshold 330m³/h)',
severity: 'warning',
uuid: 'abc',
createdAt: 1000,
message: 'FIT-7513',
thing: { id: 'dcs-1', type: 'dcs-siemens', code: 'PCS7', tags: ['siemens'], info: { container: 'cont-A' } }
}

const result = flattenHistoryAlert(alert)
t.is(result.message, 'FIT-7513', 'should expose the device tag in the history payload')
})

const dcsThings = () => [
{
id: 'dcs-1',
type: 'dcs-siemens',
code: 'PCS7',
info: { container: 'cont-A' },
last: {
alerts: [
{ severity: 'warning', name: 'flow_warning', message: 'FIT-7513' },
{ severity: 'critical', name: 'flow_alarm', message: 'FIT-7514' }
]
}
}
]

test('extractAlertsFromThings - exposes deviceId (alias of thing id)', (t) => {
const things = [
{ id: 'dcs-1', type: 'dcs-siemens', code: 'PCS7', info: {}, last: { alerts: [{ severity: 'high', name: 'flow_alarm' }] } }
]
const result = extractAlertsFromThings(things)
t.is(result[0].deviceId, 'dcs-1', 'should expose deviceId so the deviceId filter works')
t.is(result[0].id, 'dcs-1', 'should keep id for backward compatibility')
})

test('getSiteAlerts - filters by deviceId', async (t) => {
const mockCtx = createMockCtxWithOrks([{ rpcPublicKey: 'key1' }], async () => [
{ id: 'dcs-1', type: 'dcs-siemens', code: 'PCS7', info: { container: 'cont-A' }, last: { alerts: [{ severity: 'high', name: 'a1' }] } },
{ id: 'miner-9', type: 'miner', code: 'S19', info: { container: 'cont-B' }, last: { alerts: [{ severity: 'low', name: 'a2' }] } }
])
const mockReq = { query: { filter: JSON.stringify({ deviceId: 'dcs-1' }) } }

const result = await getSiteAlerts(mockCtx, mockReq)
t.is(result.total, 1, 'should filter to the one device')
t.is(result.alerts[0].deviceId, 'dcs-1', 'should return only the dcs-1 alert')
})

test('getSiteAlerts - filters by exact device tag (message)', async (t) => {
const mockCtx = createMockCtxWithOrks([{ rpcPublicKey: 'key1' }], async () => dcsThings())
const mockReq = { query: { filter: JSON.stringify({ message: 'FIT-7513' }) } }

const result = await getSiteAlerts(mockCtx, mockReq)
t.is(result.total, 1, 'should filter to the one matching tag')
t.is(result.alerts[0].message, 'FIT-7513', 'should return the FIT-7513 alert')
})

test('getSiteAlerts - filters by multiple device tags (array)', async (t) => {
const mockCtx = createMockCtxWithOrks([{ rpcPublicKey: 'key1' }], async () => dcsThings())
const mockReq = { query: { filter: JSON.stringify({ message: ['FIT-7513', 'FIT-7514'] }) } }

const result = await getSiteAlerts(mockCtx, mockReq)
t.is(result.total, 2, 'should match both tags')
})

test('getSiteAlerts - searches by device tag (message)', async (t) => {
const mockCtx = createMockCtxWithOrks([{ rpcPublicKey: 'key1' }], async () => dcsThings())
const mockReq = { query: { search: 'fit-7514' } }

const result = await getSiteAlerts(mockCtx, mockReq)
t.is(result.total, 1, 'should match one alert by tag substring (case-insensitive)')
t.is(result.alerts[0].message, 'FIT-7514', 'should return the FIT-7514 alert')
})

const dcsHistory = () => [
{
uuid: 'h1',
createdAt: 1000,
severity: 'warning',
name: 'flow_warning',
description: 'Cooling-loop flow below warning threshold — FIT-7513: 320m³/h (threshold 330m³/h)',
message: 'FIT-7513',
thing: { id: 'dcs-1', type: 'dcs-siemens', code: 'PCS7', tags: ['siemens'], info: { container: 'cont-A' } }
},
{
uuid: 'h2',
createdAt: 2000,
severity: 'critical',
name: 'flow_alarm',
description: 'Cooling-loop flow below alarm threshold — FIT-7514: 295m³/h (threshold 300m³/h)',
message: 'FIT-7514',
thing: { id: 'dcs-1', type: 'dcs-siemens', code: 'PCS7', tags: ['siemens'], info: { container: 'cont-A' } }
}
]

test('getAlertsHistory - filters by exact device tag (message)', async (t) => {
const mockCtx = createMockCtxWithOrks([{ rpcPublicKey: 'key1' }], async () => dcsHistory())
const mockReq = { query: { start: 1, end: 5000, filter: JSON.stringify({ message: 'FIT-7514' }) } }

const result = await getAlertsHistory(mockCtx, mockReq)
t.is(result.total, 1, 'should filter history to the matching tag')
t.is(result.alerts[0].message, 'FIT-7514', 'should return the FIT-7514 history alert')
})

test('getAlertsHistory - searches by device tag (message)', async (t) => {
const mockCtx = createMockCtxWithOrks([{ rpcPublicKey: 'key1' }], async () => dcsHistory())
const mockReq = { query: { start: 1, end: 5000, search: 'FIT-7513' } }

const result = await getAlertsHistory(mockCtx, mockReq)
t.is(result.total, 1, 'should find one history alert by tag')
t.is(result.alerts[0].message, 'FIT-7513', 'should return the FIT-7513 history alert')
})
Loading
Loading