Skip to content

Commit

Permalink
Merge pull request #2367 from flowforge/ha-logs
Browse files Browse the repository at this point in the history
Allow filtering of Node-RED logs when in HA mode
  • Loading branch information
joepavitt committed Jun 30, 2023
2 parents 6fc8300 + 419239d commit 36fd7a9
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 8 deletions.
15 changes: 10 additions & 5 deletions forge/containers/stub/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,27 +228,32 @@ module.exports = {
{
level: 'system',
msg: 'Fake Log Entry',
ts: `${Date.now() - oneHour}`
ts: `${Date.now() - oneHour}`,
src: 'one'
},
{
level: 'system',
msg: 'Starting Node-RED',
ts: `${Date.now() - oneHour / 2}`
ts: `${Date.now() - oneHour / 2}`,
src: 'one'
},
{
level: 'info',
msg: '\n\nMulti Line Message\n===================\n',
ts: `${Date.now() - oneHour / 4}`
ts: `${Date.now() - oneHour / 4}`,
src: 'one'
},
{
level: 'warn',
msg: 'This is the voice of the Mysterons. We know that you can hear us Earthmen.',
ts: `${Date.now() - oneHour / 5}`
ts: `${Date.now() - oneHour / 5}`,
src: 'one'
},
{
level: 'error',
msg: 'Captain Scarlet is indestructible',
ts: `${Date.now()}`
ts: `${Date.now()}`,
src: 'two'
}
]
},
Expand Down
30 changes: 28 additions & 2 deletions frontend/src/pages/instance/Logs.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
<template>
<div class="mb-3">
<SectionTopMenu hero="Node-RED Logs" info=""></SectionTopMenu>
<SectionTopMenu hero="Node-RED Logs" info="">
<template v-if="instance.ha?.replicas != undefined" #tools>
<div style="display: flex;align-items: center;">
<div class="mr-2"><strong>Replica:</strong></div>
<ff-dropdown ref="dropdown" v-model="selectedHAId" data-el="select-ha-replica">
<ff-dropdown-option label="All" value="all" />
<ff-dropdown-option
v-for="id in haIds" :key="id"
:label="id" :value="id"
/>
</ff-dropdown>
</div>
</template>
</SectionTopMenu>
</div>
<LogsShared :instance="instance" />
<LogsShared :instance="instance" :filter="selectedHAId" @ha-instance-detected="newHAId" />
</template>

<script>
Expand All @@ -22,6 +35,19 @@ export default {
type: Object,
required: true
}
},
data () {
return {
haIds: [],
selectedHAId: 'all'
}
},
methods: {
newHAId (id) {
if (!this.haIds.includes(id)) {
this.haIds.push(id)
}
}
}
}
</script>
26 changes: 25 additions & 1 deletion frontend/src/pages/instance/components/InstanceLogs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
<div v-if="prevCursor" class="flex">
<a class=" text-center w-full hover:text-blue-400 cursor-pointer pb-1" @click="loadPrevious">Load earlier...</a>
</div>
<div v-for="(item, itemIdx) in logEntries" :key="itemIdx" class="flex" :class="'forge-log-entry-level-' + item.level">
<div
v-for="(item, itemIdx) in filteredLogEntries" :key="itemIdx"
data-el="instance-log-row"
class="flex" :class="'forge-log-entry-level-' + item.level"
>
<div v-if="instance.ha?.replicas !== undefined" class="w-14 flex-shrink-0">[{{ item.src }}]</div>
<div class="w-40 flex-shrink-0">{{ item.date }}</div>
<div class="w-20 flex-shrink-0 align-right">[{{ item.level }}]</div>
<div class="flex-grow break-all whitespace-pre-wrap">{{ item.msg }}</div>
Expand All @@ -34,8 +39,14 @@ export default {
instance: {
type: Object,
required: true
},
filter: {
default: null,
type: String,
required: false
}
},
emits: ['ha-instance-detected'],
data () {
return {
doneInitialLoad: false,
Expand All @@ -47,6 +58,16 @@ export default {
showOfflineBanner: false
}
},
computed: {
filteredLogEntries: function () {
if (this.filter && this.filter !== 'all') {
const filteredList = this.logEntries.filter(l => l.src === this.filter)
return filteredList
} else {
return this.logEntries
}
}
},
timers: {
pollTimer: { time: POLL_TIME, repeat: true, autostart: false }
},
Expand Down Expand Up @@ -105,6 +126,9 @@ export default {
} else {
toPrepend.push(l)
}
if (l.src) {
this.$emit('ha-instance-detected', l.src)
}
})
if (toPrepend.length > 0) {
this.logEntries = toPrepend.concat(this.logEntries)
Expand Down
99 changes: 99 additions & 0 deletions test/e2e/frontend/cypress/tests-ee/instances/logs.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
describe('FlowForge - Instance - Logs', () => {
let instance
function navigateToInstanceLogs (teamName, instanceName) {
cy.request('GET', '/api/v1/user/teams')
.then((response) => {
const team = response.body.teams.find(
(team) => team.name === teamName
)
return cy.request('GET', `/api/v1/teams/${team.id}/projects`)
})
.then((response) => {
instance = response.body.projects.find(
(app) => app.name === instanceName
)
cy.visit(`/instance/${instance.id}/logs`)
cy.wait('@getInstance')
})
}

beforeEach(() => {
cy.intercept('GET', '/api/*/projects/*/logs?*', {
body: {
meta: {},
log: []
}
}).as('getLogsUpdates')

cy.intercept('GET', '/api/*/projects/*').as('getInstance')

cy.login('bob', 'bbPassword')
cy.home()
})

it('load all logs by default', () => {
// mock the API response with well-defined logs
cy.intercept('GET', '/api/*/projects/*/logs', {
body: {
meta: {},
log: [
{ level: 'info', msg: 'Updated flows', ts: '16874362594840001' }
]
}
}).as('getLogs')

navigateToInstanceLogs('BTeam', 'instance-2-1')
cy.wait('@getLogs')

cy.get('[data-el="instance-log-row"]').should('have.length', 1)
cy.get('[data-el="select-ha-replica"]').should('not.exist')
})

it('display a marker to indicuate with HA replica the logs are from, if present', () => {
// Modify our Instance so that HA is enabled
cy.intercept('GET', '/api/*/projects/*', (req) => {
req.continue((res) => {
// intercept the response and update the HA settings
res.body.ha = {
replicas: ['123', '456']
}
})
}).as('getInstance')

// mock the API response with well-defined logs
cy.intercept('GET', '/api/*/projects/*/logs', {
body: {
meta: {},
log: [
{ src: '123', level: 'info', msg: 'Created flows', ts: '16874362594840000' },
{ src: '123', level: 'info', msg: 'Updated flows', ts: '16874362594840001' },
{ src: '456', level: 'info', msg: 'Updated flows', ts: '16874362594840001' }
]
}
}).as('getLogs')

navigateToInstanceLogs('BTeam', 'instance-2-1')
cy.wait('@getLogs')

// should load our stubbed logs
cy.get('[data-el="instance-log-row"]').should('have.length', 3)

cy.get('[data-el="select-ha-replica"]').should('exist')
// open dropdown
cy.get('[data-el="select-ha-replica"]').click()
// check we have 3 options
cy.get('[data-el="select-ha-replica"] .ff-dropdown-option').should('have.length', 3)
// select the first HA Replica
cy.get('[data-el="select-ha-replica"] .ff-dropdown-option').eq(1).click()

// logs should now be filtered to the 3 we've defined for first replica
cy.get('[data-el="instance-log-row"]').should('have.length', 2)

// open dropdown and select "All"
cy.get('[data-el="select-ha-replica"]').click()
cy.get('[data-el="select-ha-replica"] .ff-dropdown-option').eq(0).click()

// logs should be restored
cy.get('[data-el="instance-log-row"]').should('have.length', 3)
})
})

0 comments on commit 36fd7a9

Please sign in to comment.