Appearance
Dashboard ​
Overview ​
Entry point for the DAM module: The dashboard is the first screen a user sees after entering a workspace. It gives a quick view of the most important content — recent collages, recent folders, and recently uploaded assets.
Quick-action shortcuts: Workspace-permission-aware buttons for "New Asset", "New Collage", and "New Folder" appear at the top. Each button is hidden when the authenticated user's role does not grant the relevant permission.
Recents panel with tabs: The left card shows two tabs — Collages and Folders — each with card/list items linking directly into the relevant detail page.
Right rail widgets: A "Last 7 Days" bar chart (weekly insights) sits above a "Recently Added" asset list that shows thumbnails, filenames, folders, and file-type chips.
Admin alerts: The page reads
localStorageand emits EventBus events to trigger a persistent header banner when uncategorized assets exist or when white-label SMTP is not configured.Storage gate: If the workspace storage is at or above 100%, the "New Asset" quick-action card becomes non-interactive, preventing new uploads until storage is freed.
Architecture ​
The dashboard is a self-contained Nuxt page (pages/_workspace_id/dam/dashboard/index.vue). It uses the damLayout layout and is protected by five middleware guards: authCheck, check-workspace-access, checkWorkspace, can-access-dam-module, and check-if-suspended.
All data loading happens sequentially in mounted(). The page makes five independent API calls in order: common overview data, recent collages, recent folders, recently uploaded assets, and weekly insight metrics. Each call manages its own loading flag and renders skeleton loaders while its data is in flight.
Permission checks are computed properties that reach into this.$auth.user.accessibleWorkspaces for the current workspace object and call plugin-level helpers ($canCreateCollages, $canShareDownload, etc.). This keeps the template free of inline permission logic.
The weekly insights bar chart uses Chart.js 2 via a wrapper component (InsightsTabGraph.vue). Chart data is assembled in fetchWeeklyInsights(), which deep-clones a template dataset object and attaches each insight category's graph values. A custom beforeDraw plugin renders thin placeholder lines for zero-height bars.
Collage actions (rename, delete, share, permissions) are handled directly on the dashboard via a set of lazy-loaded dialogs. The Vuex store handles the actual mutations — dam/deleteCollection and dam/renameCollection — keeping backend communication out of the page component.
EventBus is used in two directions: the page listens for alertClose (to dismiss the header banner) and syncDashboard (to refresh collages or folders after external changes), and it emits alertMsgonHeader and open-add-folder-dailog / openCreateCollage to drive sibling layout components.
File Structure ​
JavaScript Files (.js) ​
mixins/fileType.js— File type detection mixin$isAudio(fileType)— returns true for audio MIME types$isVideo(fileType)— returns true for video MIME typesgetSrcPath(file)— returns the correct thumbnail or preview URL for any asset type
mixins/commonFunctions.js— Shared utility mixinchangedFields(pairs)— diffs old/new values and returns which fields changed$getWorkspaceId()— reads workspace ID from route or session$toQueryString(obj)— serialises an object to a URL query string
mixins/imageStyle.js— Dynamic image class mixinassetsListOnloadSetStyle(file, stylesObj, refPrefix)— sets landscape/portrait class after image loaddashboardCollageOnloadSetStyle(image, stylesObj, ref)— same, scoped to collage card images
mixins/amplitude-analytics.js— Analytics mixindispatchAnalytics(body)— fires an Amplitude eventtrackActivity(body, extra)— enriches and fires an activity event
store/dam.js— Vuex DAM modulestate.storage—{ used, total, percentage, available, image, audio, video }state.newFolderCount— counter reset byresetNewFolderCountmutation on dashboard load- Action
deleteCollection(collectionId)— callsDELETE digital-assets/collection/delete/:id - Action
renameCollection({ oldCollection, newName })— commitssetCollectionNamelocally - Action
downloadMultipleFiles({ files, folders, collection_id, download_name })— triggers download
Vue Component Files (.vue) ​
pages/_workspace_id/dam/dashboard/index.vue— Dashboard page- Renders quick-action cards, recents tabs, weekly insights, and recently uploaded
- Loads all data in
mounted()and cleans up EventBus listeners inbeforeDestroy - Delegates collage CRUD to Vuex actions; renders confirm/rename/share dialogs inline
components/dam/Insights/InsightsTabGraph.vue— Weekly insights widget- Tabbed Chart.js bar chart, one tab per insight category (uploads, downloads, etc.)
- Accepts a fully assembled
bar_dataChart.js dataset object per tab - Shows a 402 upgrade tooltip when a category is gated by subscription plan
components/dam/Dialogs/ShareAssetDialog.vue— Share collage dialog- Used on dashboard to share an individual collage to external recipients
- Receives
files,collection, andcollectionAssetsIdas props
components/theme/global/Dialog/ConfirmationDialog.vue— Generic confirm dialog- Used for delete-collage confirmation
- Emits
confirmandcancel
components/dam/Dialogs/PermissionDialog.vue— Collage permission dialog- Manages which portals can see a given collage (
instance_visibility) - Emits
permission-updatedwith the updated permission object
- Manages which portals can see a given collage (
components/dam/Dialogs/CreateCollageDialog.vue— Create/rename collage dialog- Dual-purpose: create new collages and edit name/description of existing ones
- Emits
submitwith{ name, description }
SVG Icon Components ​
components/svg/CollageUploadIcon.vue— Upload icon for "New Asset" quick actioncomponents/svg/CollageGridIcon.vue— Grid icon for "New Collage" quick actioncomponents/svg/CollageFolderIcon.vue— Folder icon for "New Folder" quick action and recent asset folder linkcomponents/svg/CollageEmptyIcon.vue— Illustration shown in empty Collages/Folders/Recently Added statescomponents/svg/CollageFolderLargeIcon.vue— Large folder icon in the Folders tab list viewcomponents/svg/CollageShareIcon.vue— Share option in collage context menucomponents/svg/CollageEditIcon.vue— Edit option in collage context menucomponents/svg/CollageDeleteIcon.vue— Delete option in collage context menucomponents/svg/CollageLockIcon.vue— Lock badge on restricted collages/folderscomponents/svg/CollagePortalIcon.vue— Portals option in collage context menu
WeeklyInsights Component ​
File: components/dam/Insights/InsightsTabGraph.vue
Renders a tabbed bar chart for the last 7 days of workspace activity. Each tab corresponds to one insight category returned by the API (e.g. uploaded, downloaded). Categories gated by a plan restriction show a disabled tab with an upgrade tooltip.
Features ​
- One
v-tabper insight category from the API response - Skeleton loaders while data is loading or when the insight object is empty
- Chart.js bar chart with a custom
beforeDrawplugin that renders thin placeholder lines for zero-value bars - Upgrade messaging (402 plan gate) shown inline per tab instead of globally hiding the widget
Props ​
javascript
{
mainClass: {
type: String,
required: true // CSS class(es) applied to the outer v-card
},
heading: {
type: String,
required: true // Card title text, e.g. 'Last 7 Days'
},
dataLoading: {
type: Boolean,
required: true // Shows skeleton when true
},
weeklyInsightList: {
type: Object,
required: true // Keyed by category name; each value is an insight object
// with bar_data (Chart.js dataset), card_label, code, message
},
barOptions: {
type: Object,
required: true // Chart.js options object (axes, tooltips, hover)
},
barPlugin: {
type: Array,
required: true // Chart.js plugin array (zero-bar-plugin)
}
}Usage ​
vue
<template>
<WeeklyInsights
mainClass="boxview collageTabs weekly-insights-tab h-auto"
heading="Last 7 Days"
:dataLoading="weeklyInsightLoading"
:weeklyInsightList="weeklyInsightList"
:barOptions="InsightsBarOptions"
:barPlugin="InsightBarPlugin"
/>
</template>
<script>
import { cloneDeep } from 'lodash'
export default {
components: {
WeeklyInsights: () => import('~/components/dam/Insights/InsightsTabGraph.vue'),
},
data() {
return {
weeklyInsightLoading: true,
weeklyInsightList: {},
InsightsBarOptions: { /* Chart.js options */ },
InsightBarPlugin: [{ id: 'zero-bar-plugin', beforeDraw(chart) { /* ... */ } }],
}
},
}
</script>Dashboard Page ​
File: pages/_workspace_id/dam/dashboard/index.vue
Route: /:workspace_id/dam/dashboard
Features ​
- Quick-action cards for New Asset, New Collage, and New Folder — hidden per role
- Storage full gate: disables the New Asset card when
storage.percentage >= 100 - Recents panel with Collages and Folders tabs, each with grid card / list row items
- Collage context menu: Share, Edit (rename/description), Portals (permissions), Delete
- Folder visibility lock badge when
instance_visibilityis empty - Right-rail Recently Added list with thumbnail, name, date, folder link, and file-type chip
- Weekly insights widget with Chart.js bar chart
- EventBus-driven uncategorized assets banner and SMTP configuration warning
Computed Properties ​
javascript
{
storageFull() // true when storage.percentage >= 100
storageUsedPercentage() // formatted percentage string for display
BACKEND_URL() // this.$config.backendUrl — used to detect SVG-type assets
visibleTopBtnRoutes() // filters topBtnRoutes by canUpload/canCreateCollages/canCreateFolders
canShareDownload() // $canShareDownload(workspace)
canCreateCollages() // $canCreateCollages(workspace)
canCreateFolders() // $canCreateFolders(workspace)
canDeleteContent() // $canDeleteContent(workspace)
canDeleteCollage() // $canDeleteCollage(workspace)
canEditCollageNameDescription() // $canEditCollageNameDescription(workspace)
canManageCollagePermission() // feature flag: subscription_features.collection_permission_customisation.enable
canUpdateCollagePermission() // $canManageCollagePermission(workspace)
}Key Methods ​
javascript
{
// Data loading
getOverviewData() // GET /digital-assets/dashboard/common-data — uncategorized count + SMTP state
RecentsCollages() // GET /digital-assets/new-dashboard/recently-collection
RecentsFolders() // GET /digital-assets/new-dashboard/recently-folders
loadRecentUploads() // GET /digital-assets/new-dashboard/recently-uploaded-assets
fetchWeeklyInsights() // POST /digital-assets/new-dashboard/weekly-insights
// Navigation
navigateRoute(topBtn) // Handles New Asset route push; delegates Collage/Folder to EventBus
openCollageFolder(event, flag, item_id, new_tab, item) // Resolves route for collage or folder; respects Ctrl/Cmd click
openAsset(asset_id, new_tab) // Resolves asset detail route; fires analytics; handles PWA standalone mode
// Collage CRUD
deleteCollection() // Dispatches dam/deleteCollection; removes item from local collages array
changeCollectionName(payload) // POST collection update; dispatches dam/renameCollection
shareCollection(item) // Opens ShareAssetDialog for the given collage
confirmDeleteCollection(item) // Sets collection ref and opens delete confirm dialog
openPermissions(item) // Opens PermissionDialog for the given collage
openRenameDialog(item) // Opens CreateCollageDialog in rename mode
// EventBus helpers
addCollage() // EventBus.$emit('openCreateCollage', true)
addFolderDialog() // EventBus.$emit('open-add-folder-dailog')
// Alert management
emitUncategoryAlert() // EventBus.$emit('alertMsgonHeader', { type: 'uncategoryAlert' })
emitSmtpAlert() // EventBus.$emit('alertMsgonHeader', { type: 'smtpAlert' })
closeAlert() // Persists dismiss=true to localStorage; hides uncategorized banner
closeSmtpAlert() // Persists dismiss=true to localStorage for SMTP alert
// Image helpers (from imageStyle mixin)
getImageClassAndDynamicStyle(file, dynamicStylesCard) // Returns CSS class for fitted image display
dashboardCollageOnloadSetStyle(image, stylesObj, ref) // Sets style after collage preview image loads
// Formatting
formatDate(date) // Returns 'Jan 01, 2024' locale string
removeFileExtension(filename) // Strips last dot-extension from display name
}Workflows ​
Dashboard Load Workflow ​
1. User navigates to dashboard
Route: /:workspace_id/dam/dashboard
Middleware: authCheck → check-workspace-access → checkWorkspace
→ can-access-dam-module → check-if-suspended
Layout: damLayout
↓
2. mounted() begins — contentLoad: true shows skeleton loaders for top buttons
$nextTick sets contentLoad: false to render actual quick-action cards
↓
3. getOverviewData()
GET /digital-assets/dashboard/common-data?workspace_id={id}
→ Reads/writes localStorage key `{workspace_id}` for uncategory alert state
→ Reads/writes localStorage key `{workspace_id}_smtp` for SMTP alert state
→ Commits dam/resetNewFolderCount
→ Sets uncategoryCount, uncategoryAlert, smtpAlert
→ emitUncategoryAlert() / emitSmtpAlert() if conditions met
↓
4. RecentsCollages()
GET /digital-assets/new-dashboard/recently-collection?workspace_id={id}
→ Sets collages[] with preview_images[], each image gets a generated UUID
→ collagesLoading: false
↓
5. RecentsFolders()
GET /digital-assets/new-dashboard/recently-folders?workspace_id={id}
→ Sets folderList[]
→ foldersLoading: false
↓
6. loadRecentUploads()
GET /digital-assets/new-dashboard/recently-uploaded-assets?workspace_id={id}
→ Sets recentUploadList[]
→ recentLoading: false
↓
7. fetchWeeklyInsights()
POST /digital-assets/new-dashboard/weekly-insights
Body: { date_type: 'week', from_date: 7 days ago, to_date: today }
→ Sets weeklyInsightList{} — one key per category
→ For each category: clones InsightsBarData template, attaches labels + datasets
→ weeklyInsightLoading: false
↓
8. EventBus listeners registered
alertClose → closeAlert() or closeSmtpAlert()
syncDashboard('collage') → RecentsCollages()
syncDashboard('folders') → RecentsFolders()
$nuxt.$on('update-overview-data') → getOverviewData() if >1 second since last fetch
$nuxt.$on('update-uncategory') → getOverviewData() after 2 second delayCollage Delete Workflow ​
1. User clicks Delete in collage context menu
Component: Dashboard page
Method: confirmDeleteCollection(item)
→ Sets collection = item, deleteDialog = true
↓
2. ConfirmationDialog shown
Heading: 'Confirm Delete'
Message: 'Are you sure you want to delete this <b>{name}</b> collage?'
↓
3. User confirms
Method: deleteCollection()
Vuex: dam/deleteCollection(collectionId)
API: DELETE digital-assets/collection/delete/{id}?workspace_id={id}
↓
4. On success
snackbar.success(message)
removeCollection(collectionId) — filters collages[] in place
RecentsCollages() — re-fetches to sync order
Amplitude + trackActivity events firedCollage Rename Workflow ​
1. User clicks Edit in collage context menu
Method: openRenameDialog(item)
→ Sets collection = item, renameDialog = true
↓
2. CreateCollageDialog shown with existing name and description
↓
3. User submits
Method: changeCollectionName({ name, description })
API: POST digital-assets/collection/update/{id}
Body: { workspace_id, name, description }
↓
4. On success
collection.name and collection.description updated locally
Vuex: dam/renameCollection({ oldCollection, newName }) — local commit only
snackbar.success(message)
Amplitude + trackActivity events firedAPI Integration ​
Dashboard Endpoints ​
| Method | Endpoint | Description |
|---|---|---|
GET | /digital-assets/dashboard/common-data | Overview: uncategorized count, SMTP status |
GET | /digital-assets/new-dashboard/recently-collection | Recent collages with preview images |
GET | /digital-assets/new-dashboard/recently-folders | Recent folders with asset counts |
GET | /digital-assets/new-dashboard/recently-uploaded-assets | Recent assets with thumbnails |
POST | /digital-assets/new-dashboard/weekly-insights | 7-day activity metrics by category |
POST | /digital-assets/collection/update/:id | Rename or update collage description |
Request and Response Examples ​
GET /digital-assets/dashboard/common-data ​
javascript
// Request
GET /digital-assets/dashboard/common-data?workspace_id=42
// Response
{
"data": {
"total_uncategorized": 3,
"white_label_enabled": true,
"smtp_configured": false,
"use_storage": 2147483648,
"total_storage": 10737418240,
"storage_percentage": 20.0
}
}POST /digital-assets/new-dashboard/weekly-insights ​
javascript
// Request
POST /digital-assets/new-dashboard/weekly-insights
{
"date_type": "week",
"from_date": "2024-01-08",
"to_date": "2024-01-14"
}
// Response
{
"data": {
"uploaded": {
"graph_data": {
"2024-01-08": 4,
"2024-01-09": 2,
"2024-01-10": 0,
"2024-01-11": 7,
"2024-01-12": 1,
"2024-01-13": 3,
"2024-01-14": 5
}
},
"downloaded": {
"original": { "code": 402, "message": "Upgrade to access download analytics" }
}
}
}GET /digital-assets/new-dashboard/recently-collection ​
javascript
// Request
GET /digital-assets/new-dashboard/recently-collection?workspace_id=42&url_workspace_id=42
// Response
{
"data": [
{
"id": 101,
"name": "Spring Campaign",
"description": "Brand assets for Q1",
"assets_count": 12,
"instance_visibility": [1],
"preview_images": [
{ "url": "https://cdn.example.com/thumb1.jpg", "file_type": "jpg" },
{ "url": "https://cdn.example.com/thumb2.jpg", "file_type": "png" }
]
}
]
}Component Integration ​
Embedding Dashboard Data in a Custom Component ​
vue
<template>
<div>
<!-- Weekly insights widget -->
<WeeklyInsights
mainClass="boxview collageTabs h-auto"
heading="Last 7 Days"
:dataLoading="weeklyInsightLoading"
:weeklyInsightList="weeklyInsightList"
:barOptions="chartOptions"
:barPlugin="chartPlugins"
/>
<!-- Recently uploaded list -->
<v-card outlined class="boxview mt-4">
<v-card-title>
<h5>Recently Added</h5>
</v-card-title>
<v-card-text class="py-0">
<v-list dense class="py-0">
<v-list-item
v-for="asset in recentUploadList"
:key="asset.id"
class="recently-uploaded-list"
:ripple="false"
>
<v-list-item-avatar tile size="64" class="my-0 mr-0">
<v-img :src="getSrcPath(asset)" :alt="asset.display_file_name" />
</v-list-item-avatar>
<v-list-item-content class="py-0 mx-6">
<v-list-item-subtitle class="recent-date">
{{ formatDate(asset.created_at) }}
</v-list-item-subtitle>
<v-list-item-title>
{{ removeFileExtension(asset.display_file_name) }}
</v-list-item-title>
</v-list-item-content>
<v-list-item-action class="my-0 ml-0">
<v-chip class="tags" label>{{ asset.file_type }}</v-chip>
</v-list-item-action>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { cloneDeep } from 'lodash'
import moment from 'moment'
import fileType from '~/mixins/fileType'
export default {
name: 'DashboardSummary',
mixins: [fileType],
components: {
WeeklyInsights: () => import('~/components/dam/Insights/InsightsTabGraph.vue'),
},
data() {
return {
weeklyInsightLoading: true,
weeklyInsightList: {},
recentUploadList: [],
chartOptions: {
maintainAspectRatio: false,
responsive: true,
legend: { display: false },
scales: {
xAxes: [{ gridLines: { display: false }, ticks: { display: false } }],
yAxes: [{ gridLines: { display: false }, ticks: { beginAtZero: true, display: false } }],
},
},
chartPlugins: [],
insightsBarTemplate: {
labels: [],
datasets: [
{
label: 'Graph Data',
data: [],
backgroundColor: '#A8A8F0',
borderColor: '#A8A8F0',
},
],
},
}
},
async mounted() {
await this.loadData()
},
methods: {
async loadData() {
const workspaceId = this.$route.params.workspace_id || this.$getWorkspaceId()
try {
const pastDate = new Date()
pastDate.setDate(pastDate.getDate() - 6)
const { data: insights } = await this.$axios.$post(
'/digital-assets/new-dashboard/weekly-insights',
{
date_type: 'week',
from_date: moment(pastDate).format('YYYY-MM-DD'),
to_date: moment(new Date()).format('YYYY-MM-DD'),
}
)
Object.keys(insights).forEach((key) => {
const insight = insights[key]
if (!insight.original) {
insight.card_label = key
insight.bar_data = cloneDeep(this.insightsBarTemplate)
insight.bar_data.labels = Object.keys(insight.graph_data)
insight.bar_data.datasets[0].data = Object.values(insight.graph_data)
}
})
this.weeklyInsightList = insights
} finally {
this.weeklyInsightLoading = false
}
const { data: recent } = await this.$axios.$get(
'/digital-assets/new-dashboard/recently-uploaded-assets',
{ params: { workspace_id: workspaceId, url_workspace_id: workspaceId } }
)
this.recentUploadList = recent
},
formatDate(date) {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: '2-digit',
})
},
removeFileExtension(filename) {
const i = filename.lastIndexOf('.')
return i !== -1 ? filename.substring(0, i) : filename
},
},
}
</script>Related Documentation ​
- File Upload — How assets reach the DAM from the upload page
- Collage Feature — Full collage create/edit/share flow
- Trash — Soft-delete, restore, and permanent deletion
- Mixins — Image Style — Dynamic image orientation classes
- Mixins — Amplitude Analytics — Activity and analytics tracking
- Store — DAM — Vuex module containing storage state and collection actions