Appearance
Activity Log ​
Overview ​
- Per-User Audit Trail: Admins can view a chronological log of actions taken by any workspace user — logins, asset operations, permission changes — displayed in a modal dialog directly from the user management table.
- Last 30 Days: The system surfaces activity from the trailing 30-day window per user. Older records are not shown in the UI.
- Infinite Scroll Pagination: Activity records are paginated server-side. The dialog fetches the next page automatically when the admin scrolls past 85% of the visible list, appending results without a full reload.
- Contextual Links: Each activity entry can include an optional
activity_urlthat opens the related asset, folder, or portal in a new browser tab, giving admins direct navigation to the referenced resource. - Access Control: The dialog is available to workspace admins only. It is exposed via the per-user options menu in the admin users table and is not accessible to standard workspace members or external users.
Architecture ​
The activity log feature is composed of three layers: the options menu, the parent table component, and the dialog itself.
When an admin opens the three-dot menu on any user row in the workspace settings users table, they see an "Activity Log" option. Clicking it emits a viewActivity event from usersMenuOption.vue up to wpAdminsTable.vue, which sets activeUser to the selected user object and flips user_log_dialog to true. The ActivityLogsDialog component, which is embedded at the bottom of wpAdminsTable's template, watches the dialog prop and responds by resetting its internal state and immediately fetching page one of that user's activity.
Inside the dialog, handleFetchActivities() calls GET /digital-assets/instance/user-activity with the workspace ID, the target user's ID, and the current page number. The API returns a standard paginated response with data, last_page, and current_page fields. The dialog appends each page's records to the local activities array and tracks pagination boundaries so it can stop fetching once the last page is reached. The scroll handler monitors a ref-attached container and triggers another fetch when the scroll position exceeds 85%.
Each activity row shows a formatted timestamp (MMM D, YYYY h:mm a via moment.js) and the log_details string. If activity_url is present, a small new-tab icon button appears inline, opening the link without closing the dialog.
File Structure ​
JavaScript Files (.js) ​
store/analytics/index.js— Analytics Vuex module (shared with insights)- Not directly used by activity log, but the module namespace is
analytics - Holds general date and state utilities shared across analytics-adjacent features
- Not directly used by activity log, but the module namespace is
Vue Component Files (.vue) ​
components/dam/Dialogs/Org-Settings/ActivityLogsDialog.vue— Activity log modal- Renders a 850px-wide persistent dialog with a two-column table (Date/Time + Logs)
- Fetches paginated activity via
GET /digital-assets/instance/user-activity - Infinite scroll triggered at 85% scroll depth on the list container
- Displays skeleton loaders during initial fetch and during pagination load-more
- Shows empty state (
CollageEmptyIcon) when no records are found
components/dam/table/usersMenuOption.vue— Per-user options menu- Renders a
v-menuwith action items for active users: Edit, Activity Log, Reset Password, Deactivate - Emits
viewActivitywith the user object when "Activity Log" is clicked - Renders different menu lists for active vs. pending users (pending users get Resend/Revoke options instead)
- Renders a
components/dam/table/wpAdminsTable.vue— Admin users table (dialog host)- Owns
activeUser(the user whose log to show) anduser_log_dialog(boolean dialog flag) - Mounts
ActivityLogsDialogas a persistent child component - Handles
viewActivityfromusersMenuOptionviaviewUserLog(user)method - Handles dialog close event via
closeUserLogs(), resetting both state values
- Owns
SVG Icon Components ​
components/svg/CollageEyeIcon.vue— Eye icon used for the "Activity Log" menu itemcomponents/svg/CollageNewTabIcon.vue— New-tab icon rendered next to activity entries that have a URLcomponents/svg/CollageEmptyIcon.vue— Empty state illustration when no activity is foundcomponents/svg/CollageCloseIcon.vue— Close button icon in the dialog header
ActivityLogsDialog Component ​
Features ​
- Persistent dialog (non-dismissable by clicking outside) with a close button
- Two-column layout: timestamp column (left) and log details column (65% width, right)
- New-tab link button rendered conditionally per activity row when
activity_urlis present - Initial full-screen skeleton loader (9 skeleton rows) during first fetch
- Inline load-more skeleton (6 rows) appended below the list during pagination
- Auto-reset on dialog close: clears
activities,currentPage,lastPage - Guards against duplicate fetches: skips if
lastPageis set andcurrentPage >= lastPage, or if a load-more is already in progress
Props ​
javascript
{
user: {
type: Object,
required: true // Must have at least { id: String }
},
dialog: {
type: Boolean,
default: false // Watched — opening triggers reset + fetch
}
}Events ​
javascript
{
'close': () => {} // Emitted when admin clicks the X button; parent resets dialog flag
}Methods ​
javascript
{
// Reset all pagination state and clear the activity list
reset() {},
// Close dialog, emit 'close', then reset
closeDialog() {},
// Fetch one page of activities; flag = 'default' (initial) or 'scroll' (pagination)
// Sets isLoading for initial fetch, loadMore for scroll-triggered fetches
async handleFetchActivities(flag = 'default') {},
// Scroll event handler on the list container ref
// Triggers handleFetchActivities('scroll') when scrolled >= 85%
async handleScroll() {}
}Usage ​
vue
<template>
<ActivityLogsDialog
:dialog="user_log_dialog"
:user="activeUser || {}"
@close="closeUserLogs()"
/>
</template>
<script>
export default {
components: {
ActivityLogsDialog: () =>
import('~/components/dam/Dialogs/Org-Settings/ActivityLogsDialog.vue'),
},
data() {
return {
activeUser: null,
user_log_dialog: false,
}
},
methods: {
viewUserLog(user) {
this.activeUser = user
this.user_log_dialog = true
},
closeUserLogs() {
this.user_log_dialog = false
this.activeUser = null
},
},
}
</script>usersMenuOption Component ​
Features ​
- Context menu rendered inside a
v-menuoffset-y - Two menu lists:
activeMenuList(Edit, Activity Log, Reset Password, Deactivate) andpendingMenuList(Resend Invitation, Revoke Access) - Menu items disabled based on
checkDisabled(menu)— prevents actions on users in incompatible states - All click events are defined as closures in the
data()function using EventBus or$emit
Props ​
javascript
{
user: {
type: Object,
required: true // The user row object from the parent table
},
userType: {
type: String,
default: '' // 'active-users' | 'pending-users' — controls which menu list to show
}
}Events ​
javascript
{
'viewActivity': (user) => {} // Emitted when "Activity Log" is clicked; carries full user object
}Methods ​
javascript
{
// Returns true if the menu item should be rendered disabled for this user state
checkDisabled(menu) {}
}wpAdminsTable Component (activity log surface) ​
Features ​
- Hosts
ActivityLogsDialogat the bottom of its template (always mounted, toggled via dialog prop) - Renders
usersMenuOptionper row; receivesviewActivityevents to open the log dialog - Shows a
last_activitytimestamp column for active users (formatted asMMM D, YYYY h:mm a) - Supports infinite scroll on the user list itself; emits
load-moreto the parent page
Props ​
javascript
{
userList: {
type: Array,
required: true // Array of user objects from the API
},
instances: {
type: Array,
default: () => [] // Available portal instances for name resolution
},
mainFlag: {
type: String,
default: '' // 'users-list' | 'admins-list' | 'portal-users'
},
subFlag: {
type: String,
default: 'active-users' // 'active-users' | 'pending-users' | 'deactive-users'
},
loadMore: {
type: Boolean,
default: false // Shows skeleton loader rows when true
},
showSortArrow: {
type: Boolean,
default: false
},
sortObj: {
type: Object,
default: null // { field: 'name'|'email', sort_by: 'ASC'|'DESC' }
},
sortLoaderVal: {
type: String,
default: ''
}
}Events ​
javascript
{
'sort': (field) => {}, // Emitted when a sortable column header is clicked
'load-more': () => {} // Emitted when the user list is scrolled past 85%
}Methods ​
javascript
{
// Opens ActivityLogsDialog for the given user
viewUserLog(user) {},
// Closes the dialog and clears activeUser
closeUserLogs() {},
// Scroll handler for the user list container — emits 'load-more' at 85%
async handleScroll() {},
// Maps a name string to a CSS class for avatar letter color
getFirstCharClass(item) {},
// Resolves instance_ids to portal brand names (comma-separated)
getPortalNames(user) {}
}Workflows ​
View Activity Log for a User ​
1. Admin navigates to workspace settings → users list
Route: /:workspace_id/workspace-settings/user
Page renders wpAdminsTable with mainFlag: 'users-list', subFlag: 'active-users'
↓
2. Admin clicks the three-dot menu icon on a user row
Component: usersMenuOption.vue
Renders v-menu with activeMenuList options
↓
3. Admin selects "Activity Log"
usersMenuOption emits: $emit('viewActivity', this.user)
wpAdminsTable.viewUserLog(user) called
Sets: activeUser = user, user_log_dialog = true
↓
4. ActivityLogsDialog receives dialog: true via prop
watch(dialog) fires → calls reset() then handleFetchActivities('default')
Sets: isLoading = true, currentPage = 1
↓
5. GET /digital-assets/instance/user-activity
Params: { url_workspace_id, workspace_id, page: 1, user_id }
Response: { data: [...], last_page: N, current_page: 1 }
↓
6. Dialog renders activity list
Columns: Date/Time | Logs
Each row: moment-formatted timestamp + log_details text
Optional new-tab link button if activity_url present
↓
7. Admin scrolls the activity list
handleScroll() fires on container scroll event
When scrollTop / scrollHeight >= 85%:
handleFetchActivities('scroll') called
Sets: loadMore = true, currentPage += 1
↓
8. Next page fetched
New records appended to activities array
loadMore set to false once response arrives
Stops fetching when currentPage >= lastPage
↓
9. Admin closes the dialog
Clicks X button → closeDialog() → $emit('close')
wpAdminsTable.closeUserLogs(): user_log_dialog = false, activeUser = null
Dialog.reset(): clears activities, currentPage, lastPageAPI Integration ​
Endpoints ​
| Method | Path | Description |
|---|---|---|
GET | /digital-assets/instance/user-activity | Paginated activity log for a specific user |
Fetch Activity Log Request ​
json
GET /digital-assets/instance/user-activity
?url_workspace_id=ws_abc123
&workspace_id=ws_abc123
&page=1
&user_id=usr_xyz789Fetch Activity Log Response (success) ​
json
{
"data": {
"current_page": 1,
"last_page": 3,
"data": [
{
"id": 1001,
"created_at": "2026-08-10T14:32:00.000Z",
"log_details": "Uploaded asset brand-logo.png to folder /Marketing/2026",
"activity_url": "https://app.collage.inc/ws_abc123/dam/files/asset_001"
},
{
"id": 1000,
"created_at": "2026-08-10T12:15:00.000Z",
"log_details": "Downloaded 3 assets from portal Summer Campaign",
"activity_url": null
},
{
"id": 999,
"created_at": "2026-08-09T09:00:00.000Z",
"log_details": "Logged in from Chrome on macOS",
"activity_url": null
}
]
}
}Fetch Activity Log Response (no activity) ​
json
{
"data": {
"current_page": 1,
"last_page": 1,
"data": []
}
}Component Integration ​
Full working example of the three-component stack: the settings page integrating the table, which hosts the dialog.
vue
<template>
<div>
<wpAdminsTable
:userList="userList"
:instances="portalInstances"
mainFlag="users-list"
subFlag="active-users"
:loadMore="loadingMore"
@load-more="fetchNextPage"
@sort="handleSort"
/>
</div>
</template>
<script>
export default {
layout: 'damLayout',
components: {
wpAdminsTable: () => import('~/components/dam/table/wpAdminsTable.vue'),
},
data() {
return {
userList: [],
portalInstances: [],
loadingMore: false,
currentPage: 1,
lastPage: null,
}
},
async mounted() {
await this.fetchUsers()
},
methods: {
async fetchUsers() {
try {
const { data } = await this.$axios.$get(
'/digital-assets/instance/users',
{
params: {
workspace_id: this.$route.params.workspace_id,
url_workspace_id: this.$route.params.workspace_id,
page: this.currentPage,
},
}
)
this.lastPage = data.last_page
this.userList.push(...data.data)
} catch (error) {
this.$snackbar.error(this.$getErrorMessage(error))
}
},
async fetchNextPage() {
if (this.loadingMore || (this.lastPage && this.currentPage >= this.lastPage)) return
this.loadingMore = true
this.currentPage += 1
await this.fetchUsers()
this.loadingMore = false
},
handleSort(field) {
// Re-fetch with sort params
},
},
}
</script>Related Documentation ​
- Users & Permissions — Workspace user management and roles
- Workspace Settings — Settings pages where the user table lives
- File Upload — Asset uploads generate activity log entries
- Insights — Aggregate usage charts on the dashboard