Skip to content

Commit 6cc940c

Browse files
authored
Merge pull request #4287 from FlowFuse/3818-edit-snapshot-audit-entries
Audit log entries for Snapshot Update
2 parents 4830d72 + 169d9b4 commit 6cc940c

8 files changed

Lines changed: 74 additions & 7 deletions

File tree

forge/auditLog/application.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ module.exports = {
2626
async created (actionedBy, error, application, device, snapshot) {
2727
await log('application.device.snapshot.created', actionedBy, application?.id, generateBody({ error, device, snapshot }))
2828
},
29+
async updated (actionedBy, error, application, device, snapshot, updates) {
30+
await log('application.device.snapshot.updated', actionedBy, application?.id, generateBody({ error, device, snapshot, updates }))
31+
},
2932
async deleted (actionedBy, error, application, device, snapshot) {
3033
await log('application.device.snapshot.deleted', actionedBy, application?.id, generateBody({ error, device, snapshot }))
3134
},

forge/auditLog/project.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ module.exports = {
7878
async created (actionedBy, error, project, snapshot) {
7979
await log('project.snapshot.created', actionedBy, project?.id, generateBody({ error, project, snapshot }))
8080
},
81+
async updated (actionedBy, error, project, snapshot, updates) {
82+
await log('project.snapshot.updated', actionedBy, project?.id, generateBody({ error, project, snapshot, updates }))
83+
},
8184
async rolledBack (actionedBy, error, project, snapshot) {
8285
await log('project.snapshot.rolled-back', actionedBy, project?.id, generateBody({ error, project, snapshot }))
8386
},

forge/routes/api/snapshot.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* @memberof forge.routes.api
88
*/
99

10+
const { UpdatesCollection } = require('../../auditLog/formatters.js')
11+
1012
module.exports = async function (app) {
1113
/** @type {typeof import('../../db/controllers/Snapshot.js')} */
1214
const snapshotController = app.db.controllers.Snapshot
@@ -191,14 +193,20 @@ module.exports = async function (app) {
191193
}
192194
}
193195
}, async (request, reply) => {
196+
// capture the original name/description for the audit log
197+
const snapshotBefore = { name: request.snapshot.name, description: request.snapshot.description }
198+
// perform the update
194199
const snapshot = await snapshotController.updateSnapshot(request.snapshot, request.body)
195-
// TODO: audit log
196-
// if (request.ownerType === 'device') {
197-
// const application = await request.owner.getApplication()
198-
// await applicationLogger.application.device.snapshot.updated(request.session.User, null, application, request.owner, request.snapshot, snapshot)
199-
// } else if (request.ownerType === 'instance') {
200-
// await projectLogger.project.snapshot.updated(request.session.User, null, request.owner, request.snapshot, snapshot)
201-
// }
200+
// log the update
201+
const snapshotAfter = { name: snapshot.name, description: snapshot.description }
202+
const updates = new UpdatesCollection()
203+
updates.pushDifferences(snapshotBefore, snapshotAfter)
204+
if (request.ownerType === 'device') {
205+
const application = await request.owner.getApplication()
206+
await applicationLogger.application.device.snapshot.updated(request.session.User, null, application, request.owner, request.snapshot, updates)
207+
} else if (request.ownerType === 'instance') {
208+
await projectLogger.project.snapshot.updated(request.session.User, null, request.owner, request.snapshot, updates)
209+
}
202210
reply.send(projectSnapshotView.snapshot(snapshot))
203211
})
204212

frontend/src/components/audit-log/AuditEntryIcon.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ const iconMap = {
167167
],
168168
clock: [
169169
'project.snapshot.created',
170+
'project.snapshot.updated',
170171
'project.device.snapshot.created',
171172
'project.snapshot.deleted',
172173
'project.snapshot.rollback',
@@ -176,6 +177,7 @@ const iconMap = {
176177
'project.snapshot.device-target-set',
177178
'project.snapshot.deviceTarget', // legacy event
178179
'application.device.snapshot.created',
180+
'application.device.snapshot.updated',
179181
'application.device.snapshot.deleted',
180182
'application.device.snapshot.exported',
181183
'application.device.snapshot.imported',

frontend/src/components/audit-log/AuditEntryVerbose.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,11 @@
392392
<span v-if="!error && entry.body?.device && entry.body.snapshot">Snapshot '{{ entry.body.snapshot?.name }}' has been been created from Application owned Device '{{ entry.body.device?.name }}'.</span>
393393
<span v-else-if="!error">Device or Snapshot data not found in audit entry.</span>
394394
</template>
395+
<template v-else-if="entry.event === 'application.device.snapshot.updated'">
396+
<label>{{ AuditEvents[entry.event] }}</label>
397+
<span v-if="!error && entry.body && entry.body.updates">Snapshot '{{ entry.body.snapshot?.name }}' of Application owned Device '{{ entry.body.device?.name }}' has been been updated has with following changes: <AuditEntryUpdates :updates="entry.body.updates" /></span>
398+
<span v-else-if="!error">Change data not found in audit entry.</span>
399+
</template>
395400
<template v-else-if="entry.event === 'application.device.snapshot.deleted'">
396401
<label>{{ AuditEvents[entry.event] }}</label>
397402
<span v-if="!error && entry.body?.device && entry.body.snapshot">Snapshot '{{ entry.body.snapshot?.name }}' has been been deleted for Application owned Device '{{ entry.body.device?.name }}'.</span>
@@ -541,6 +546,11 @@
541546
<span v-if="!error && entry.body?.project && entry.body.snapshot">A new Snapshot '{{ entry.body.snapshot?.name }}' has been created for Instance '{{ entry.body.project?.name }}'.</span>
542547
<span v-else-if="!error">Instance data not found in audit entry.</span>
543548
</template>
549+
<template v-else-if="entry.event === 'project.snapshot.updated'">
550+
<label>{{ AuditEvents[entry.event] }}</label>
551+
<span v-if="!error && entry.body && entry.body.updates">Snapshot '{{ entry.body.snapshot?.name }}' of Instance '{{ entry.body.project?.name }}' has been been updated has with following changes: <AuditEntryUpdates :updates="entry.body.updates" /></span>
552+
<span v-else-if="!error">Change data not found in audit entry.</span>
553+
</template>
544554
<template v-else-if="entry.event === 'project.device.snapshot.created'">
545555
<label>{{ AuditEvents[entry.event] }}</label>
546556
<span v-if="!error && entry.body?.project && entry.body.snapshot">A new Snapshot '{{ entry.body.snapshot?.name }}' has been created from Device '{{ entry.body.device?.name }}' for Instance '{{ entry.body.project?.name }}'.</span>

frontend/src/data/audit-events.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"application.device.assigned": "Device Assigned to Application",
4848
"application.device.unassigned": "Device Unassigned from Application",
4949
"application.device.snapshot.created": "Device Snapshot Created",
50+
"application.device.snapshot.updated": "Device Snapshot Updated",
5051
"application.device.snapshot.deleted": "Device Snapshot Deleted",
5152
"application.device.snapshot.exported": "Device Snapshot Exported",
5253
"application.device.snapshot.imported": "Snapshot Imported",
@@ -74,6 +75,7 @@
7475
"project.stack.changed": "Instance Stack Changed",
7576
"project.settings.updated": "Instance Settings Updated",
7677
"project.snapshot.created": "Instance Snapshot Created",
78+
"project.snapshot.updated": "Instance Snapshot Updated",
7779
"project.device.snapshot.created": "Device Snapshot Created",
7880
"project.snapshot.rolled-back": "Instance Rolled Back",
7981
"project.snapshot.rollback": "Instance Rolled Back",

test/unit/forge/auditLog/application_spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,24 @@ describe('Audit Log > Application', async function () {
151151
logEntry.body.snapshot.id.should.equal(SNAPSHOT.hashid)
152152
})
153153

154+
it('Provides a logger for application device snapshot updated', async function () {
155+
const updates = new UpdatesCollection()
156+
updates.pushDifferences({ name: 'snapshot' }, { name: 'snapshot-new-name' })
157+
await logger.application.device.snapshot.updated(ACTIONED_BY, null, APPLICATION, DEVICE, SNAPSHOT, updates)
158+
// check log stored
159+
const logEntry = await getLog()
160+
logEntry.should.have.property('event', 'application.device.snapshot.updated')
161+
logEntry.should.have.property('scope', { id: APPLICATION.hashid, type: 'application' })
162+
logEntry.should.have.property('trigger', { id: ACTIONED_BY.hashid, type: 'user', name: ACTIONED_BY.username })
163+
logEntry.should.have.property('body')
164+
logEntry.body.should.only.have.keys('device', 'snapshot', 'updates')
165+
logEntry.body.device.should.only.have.keys('id', 'name')
166+
logEntry.body.device.id.should.equal(DEVICE.hashid)
167+
logEntry.body.snapshot.should.only.have.keys('id', 'name')
168+
logEntry.body.snapshot.id.should.equal(SNAPSHOT.hashid)
169+
logEntry.body.updates.should.be.an.Array().and.have.length(1)
170+
})
171+
154172
it('Provides a logger for application device snapshot deleted', async function () {
155173
await logger.application.device.snapshot.deleted(ACTIONED_BY, null, APPLICATION, DEVICE, SNAPSHOT)
156174
// check log stored

test/unit/forge/auditLog/project_spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const should = require('should') // eslint-disable-line
2+
const { UpdatesCollection } = require('../../../../forge/auditLog/formatters')
3+
24
const FF_UTIL = require('flowforge-test-utils')
35
// Declare a dummy getLoggers function for type hint only
46
/** @type {import('../../../../forge/auditLog/project').getLoggers} */
@@ -286,6 +288,25 @@ describe('Audit Log > Project', async function () {
286288
logEntry.body.snapshot.id.should.equal(SNAPSHOT.hashid)
287289
})
288290

291+
it('Provides a logger for updating snapshots of a project', async function () {
292+
const updates = new UpdatesCollection()
293+
updates.pushDifferences({ name: 'old' }, { name: 'new' })
294+
await projectLogger.project.snapshot.updated(ACTIONED_BY, null, PROJECT, SNAPSHOT, updates)
295+
// check log stored
296+
const logEntry = await getLog()
297+
logEntry.should.have.property('event', 'project.snapshot.updated')
298+
logEntry.should.have.property('scope', { id: PROJECT.id, type: 'project' })
299+
logEntry.should.have.property('trigger', { id: ACTIONED_BY.hashid, type: 'user', name: ACTIONED_BY.username })
300+
logEntry.should.have.property('body')
301+
logEntry.body.should.only.have.keys('project', 'snapshot', 'updates')
302+
logEntry.body.project.should.only.have.keys('id', 'name')
303+
logEntry.body.project.id.should.equal(PROJECT.id)
304+
logEntry.body.snapshot.should.only.have.keys('id', 'name')
305+
logEntry.body.snapshot.id.should.equal(SNAPSHOT.hashid)
306+
logEntry.body.updates.should.have.length(1)
307+
logEntry.body.updates[0].should.eql({ key: 'name', old: 'old', new: 'new', dif: 'updated' })
308+
})
309+
289310
it('Provides a logger for rolling back a snapshot of a project', async function () {
290311
await projectLogger.project.snapshot.rolledBack(ACTIONED_BY, null, PROJECT, SNAPSHOT)
291312
// check log stored

0 commit comments

Comments
 (0)