Umbraco CMS
CloudHeartcoreDXPMarketplace
13.latest (LTS)
13.latest (LTS)
  • Umbraco CMS Documentation
  • Legacy Documentation
    • Umbraco 11 Documentation
    • Umbraco 8 Documentation
    • Umbraco 7 Documentation
  • Release Notes
  • Contribute
  • Sustainability Best Practices
  • Fundamentals
    • Get to know Umbraco
    • Setup
      • Requirements
      • Installation
        • Install using .NET CLI
        • Install using Visual Studio
        • Local IIS With Umbraco
        • Install using Visual Studio Code
        • Installing Nightly Builds
        • Running Umbraco on Linux/macOS
        • Unattended Installs
      • Upgrade your project
        • Version Specific Upgrades
          • Upgrade from Umbraco 8 to the latest version
          • Migrate content to Umbraco 8
          • Minor upgrades for Umbraco 8
          • Upgrade to Umbraco 7
          • Minor upgrades for Umbraco 7
      • Server setup
        • Running Umbraco On Azure Web Apps
        • Hosting Umbraco in IIS
        • File And Folder Permissions
        • Runtime Modes
        • Running Umbraco in Docker
        • Umbraco in Load Balanced Environments
          • Load Balancing Azure Web Apps
          • Standalone File System
          • Advanced Techniques With Flexible Load Balancing
          • Logging With Load Balancing
    • Backoffice
      • Sections
      • Property Editors
        • Built-in Property Editors
          • Checkbox List
          • Color Picker
          • Content Picker
          • DateTime
          • Date
          • Decimal
          • Email Address
          • Eye Dropper Color Picker
          • File Upload
          • Image Cropper
          • Label
          • List View
          • Markdown Editor
          • Media Picker
          • Media Picker (Legacy)
          • Member Group Picker
          • Member Picker
          • Multi Url Picker
          • Multinode Treepicker
          • Repeatable Textstrings
          • Numeric
          • Radiobutton List
          • Slider
          • Tags
          • Textarea
          • Textbox
          • Toggle
          • User Picker
          • Block Editors
            • Block Grid
            • Block List
            • Build a Custom View for a Block
            • Configuring Block Editor Label Properties
          • Dropdown
          • Grid Layout (Legacy)
            • What Are Grid Layouts?
            • Configuring The Grid Layout
            • Settings And Styling
            • Grid Editors
            • Build Your Own Editor
            • Rendering Grid In a Template
            • Grid Layout Best Practices
            • Add Values Programmatically
          • Rich Text Editor
            • Rich Text Editor Configuration
            • Rich Text Editor Styles
            • Rich Text Editor Plugins
            • Blocks in Rich Text Editor
      • Login
      • Content Templates
      • Infinite Editing
      • Log Viewer
      • Language Variants
      • Settings Dashboards
    • Data
      • Defining Content
        • Default Document Types
        • Document Type Localization
      • Creating Media
        • Default Data/Media Types
      • Members
      • Data Types
        • Default Data Types
      • Scheduled Publishing
      • Using Tabs
      • Users
      • Relations
      • Dictionary Items
      • Content Version Cleanup
    • Design
      • Templates
        • Basic Razor Syntax
        • Named Sections
        • Razor Cheatsheet
      • Rendering Content
      • Rendering Media
      • Partial Views
      • Partial View Macro Files
      • Stylesheets And JavaScript
    • Code
      • Service APIs
      • Subscribing To Notifications
      • Creating Forms
      • Debugging
        • Logging
      • Source Control
  • Implementation
    • Learn how Umbraco works
    • Routing
      • Controller & Action Selection
      • Execute Request
      • Request Pipeline
    • Custom Routing
      • Adding a hub with SignalR and Umbraco
    • Controllers
    • Data Persistence (CRUD)
    • Composing
    • Integration Testing
    • Nullable Reference Types
    • Services and Helpers
      • Circular Dependencies
    • Unit Testing
  • Extending
    • Customize the editing experience
    • Dashboards
    • Sections & Trees
      • Sections
      • Trees
        • Tree Actions
      • Searchable Trees (ISearchableTree)
    • Property Editors
      • Property Value Converters
      • Property Actions
      • Tracking References
      • Declaring your property editor
      • Content Picker Value Converter Example
    • Package Manifest
    • Macro Parameter Editors
    • Health Check
      • Health Check Guides
        • Click-Jacking Protection
        • Content/MIME Sniffing Protection
        • Cross-site scripting Protection (X-XSS-Protection header)
        • Debug Compilation Mode
        • Excessive Headers
        • Fixed Application Url
        • Folder & File Permissions
        • HTTPS Configuration
        • Macro Errors
        • Notification Email Settings
        • SMTP
        • Strict-Transport-Security Header
    • Language Files & Localization
    • Backoffice Search
    • Backoffice Tours
    • Backoffice UI API Documentation
    • Content Apps
    • Creating a Custom Database Table
    • Embedded Media Providers
    • Custom File Systems (IFileSystem)
      • Using Azure Blob Storage for Media and ImageSharp Cache
    • Configuring Azure Key Vault
    • Packages
      • Creating a Package
      • Language file for packages
      • Listing a Package on the Umbraco Marketplace
      • Good practice and defaults
      • Packages on Umbraco Cloud
      • Installing and Uninstalling Packages
      • Maintaining packages
      • Create accessible Umbraco packages
      • Example Package Repository
    • UI Library
  • Reference
    • Dive into the code
    • Configuration
      • Basic Authentication Settings
      • Connection strings settings
      • Content Dashboard Settings
      • Content Settings
      • Data Types Settings
      • Debug settings
      • Examine settings
      • Exception filter settings
      • FileSystemProviders Configuration
      • Global Settings
      • Health checks
      • Hosting settings
      • Imaging settings
      • Indexing settings
      • Install Default Data Settings
      • Keep alive settings
      • Logging settings
      • Maximum Upload Size Settings
      • Models builder settings
      • NuCache Settings
      • Package Migration
      • Plugins settings
      • Request handler settings
      • Rich text editor settings
      • Runtime minification settings
      • Runtime settings
      • Security Settings
      • Serilog settings
      • Tours settings
      • Type finder settings
      • Unattended
      • Web routing
    • Templating
      • Macros
        • Managing macros
        • Partial View Macros
      • Models Builder
        • Introduction
        • Configuration
        • Builder Modes
        • Understand and Extend
        • Using Interfaces
        • Tips and Tricks
      • Working with MVC
        • Working with MVC Views in Umbraco
        • View/Razor Examples
        • Using MVC Partial Views in Umbraco
        • Using View Components in Umbraco
        • Querying & Traversal
        • Creating Forms
    • Querying & Models
      • IMemberManager
      • IPublishedContentQuery
      • ITagQuery
      • UDI Identifiers
      • UmbracoContext helper
      • UmbracoHelper
      • IPublishedContent
        • IPublishedContent Collections
        • IPublishedContent IsHelpers
        • IPublishedContent Property Access & Extension Methods
    • Routing & Controllers
      • Routing requirements for backoffice authentication
      • Custom MVC controllers (Umbraco Route Hijacking)
      • Custom MVC Routes
      • Custom Middleware
      • URL Rewrites in Umbraco
      • Special Property Type aliases for routing
      • URL Redirect Management
      • Routing in Umbraco
        • FindPublishedContentAndTemplate()
        • IContentFinder
        • Inbound request pipeline
        • Outbound request pipeline
        • Published Content Request Preparation
      • Surface controllers
        • Surface controller actions
      • Umbraco API Controllers
        • Umbraco Api - Authorization
        • Umbraco Api - Routing & Urls
    • Content Delivery API
      • Custom property editors support
      • Extension API for querying
      • Media Delivery API
      • Protected content in the Delivery API
      • Output caching
      • Property expansion and limiting
      • Additional preview environments support
    • Webhooks
      • Expanding Webhook Events
    • API versioning and OpenAPI
    • Searching
      • Examine
        • Examine Management
        • Examine Manager
        • Custom indexing
        • PDF indexes and multisearchers
        • Quick-start
    • Using Notifications
      • Notification Handler
      • CacheRefresher Notifications Example
      • ContentService Notifications Example
      • Creating And Publishing Notifications
      • Determining if an entity is new
      • MediaService Notifications Example
      • MemberService Notifications Example
      • Sending Allowed Children Notification
      • Umbraco Application Lifetime Notifications
      • EditorModel Notifications
        • Customizing the "Links" box
      • Hot vs. cold restarts
    • Inversion of Control / Dependency injection
    • Management
      • Models Reference
        • Content
        • ContentType
        • DataType
        • DictionaryItem
        • Language
        • Media
        • MediaType
        • Relation
        • RelationType
        • ServerRegistration
        • Template
      • Services Reference
        • AuditService
        • ConsentService
        • DataTypeService
        • DomainService
        • EntityService
        • ExternalLoginService
        • FileService
        • MacroService
        • MediaService
        • MemberGroupService
        • MemberService
        • MemberTypeService
        • NotificationService
        • PackagingService
        • PublicAccessService
        • RedirectUrlService
        • RelationService
        • ServerRegistrationService
        • TagService
        • TextService
        • ContentService
          • Create content programmatically
          • Publish content programmatically
        • ContentTypeService
          • Retrieving content types
          • Retrieving content types
        • LocalizationService
          • Retrieving languages
        • UserService
          • Creating a user
    • Plugins
      • Creating Resolvers
      • Finding types
    • Cache & Distributed Cache
      • Accessing the cache
      • ICacheRefresher
      • IServerMessenger
      • Getting/Adding/Updating/Inserting Into Cache
      • Examples
        • Working with caching
    • Response Caching
    • Security
      • API rate limiting
      • BackOfficeUserManager and Events
      • Cookies
      • Replacing the basic username/password check
      • External login providers
      • Locking of Users and password reset
      • Reset admin password
      • Umbraco Security Hardening
      • Umbraco Security Settings
      • Sensitive data
      • Sanitizing the Rich Text Editor
      • Setup Umbraco for a FIPS Compliant Server
      • HTTPS
      • Two-factor Authentication
      • Server-side file validation
    • Scheduling
    • Common Pitfalls & Anti-Patterns
    • API Documentation
    • Debugging with SourceLink
    • Language Variation
    • UmbracoMapper
    • Distributed Locks
    • AngularJS
      • Directives
        • umbLayoutSelector
        • umbLoadIndicator
        • umbProperty
      • Services
        • Editor Service
        • Events Service
          • changeTitle
  • Tutorials
    • Overview
    • Creating a Basic Website
      • Getting Started
      • Document Types
      • Creating Your First Template
      • CSS and Images
      • Displaying the Document Type Properties
      • Creating a Master Template
      • Creating Pages and Using the Master Template
      • Setting the Navigation Menu
      • Articles and Article Items
      • Adding Language Variants
      • Conclusions
    • Creating a Custom Dashboard
      • Extending the Dashboard using the Umbraco UI library
    • Creating a Property Editor
      • Adding configuration to a property editor
      • Integrating services with a property editor
      • Adding server-side data to a property editor
    • Creating a Multilingual Site
    • Add Google Authentication (Users)
    • Add Microsoft Entra ID authentication (Members)
    • Creating a Backoffice Tour
    • Creating Custom Database Tables with Entity Framework
    • The Starter Kit
      • Lessons
        • Customize the Starter Kit
        • Add a Blog Post Publication Date
          • Add a Blog Post Publication Date
          • Add a Blog Post Publication Date
        • Add Open Graph
          • Add Open Graph - Step 1
          • Add Open Graph - Step 2
          • Add Open Graph - Step 3
          • Add Open Graph - Step 4
          • Add Open Graph - Summary
        • Ask For Help and Join the Community
    • Editor's Manual
      • Getting Started
        • Logging In and Out
        • Umbraco Interface
        • Creating, Saving and Publishing Content Options
        • Finding Content
        • Editing Existing Content
        • Sorting Pages
        • Moving a Page
        • Copying a Page
        • Deleting and Restoring Pages
      • Working with Rich Text Editor
      • Version Management
        • Comparing Versions
        • Rollback to a Previous Version
      • Media Management
        • Working with Folders
        • Working with Media Types
        • Cropping Images
      • Tips & Tricks
        • Refreshing the Tree View
        • Audit Trail
        • Notifications
        • Preview Pane Responsive View
        • Session Timeout
    • Multisite Setup
    • Member Registration and Login
    • Custom Views for Block List
    • Connecting Umbraco Forms and Zapier
    • Creating an XML Sitemap
    • Implementing Custom Error Pages
Powered by GitBook
On this page
  • What is a Dashboard?
  • Why provide a Custom Dashboard for editors?
  • Prerequisites
  • Step 1: Creating and configuring the Dashboard
  • Step 2: Add Language Keys
  • Different languages
  • Step 3: Adding style
  • Step 4: Adding functionality
  • Step 5: Using Umbraco Angular Services and Directives
  • Add a list of edited articles
  • Creating a shortcut for creating new content
  • Extending with Custom External Data
  • Going Further
Edit on GitHub
Export as PDF
  1. Tutorials

Creating a Custom Dashboard

A guide that shows you how you can create a custom dashboard in Umbraco CMS.

PreviousConclusionsNextExtending the Dashboard using the Umbraco UI library

Last updated 1 year ago

What is a Dashboard?

A Dashboard is a tab on the right-hand side of a section eg. the Getting Started dashboard in the Content section:

Why provide a Custom Dashboard for editors?

It is generally considered good practice to provide a custom dashboard to welcome your editors to the backoffice of your site. You can provide information about the site and/or provide a helpful gateway to common functionality the editors will use. This guide will show the basics of creating a custom 'Welcome Message' dashboard. The guide will also show how you can go a little further to provide interaction using AngularJS.

The finished dashboard will give the editors an overview of which pages and media files they've worked on most recently.

Prerequisites

This tutorial uses AngularJS with Umbraco, so it does not cover AngularJS itself, there are tons of resources on that already here:

  • Egghead.io

  • AngularJS.org/tutorial

  • Pluralsight

There are a lot of parallels with Creating a Property Editor. The tutorial Creating a Property Editor Tutorial is worth a read too.

At the end of this guide, we should have a friendly welcoming dashboard displaying a list of the editor's recent site updates.

Step 1: Creating and configuring the Dashboard

  1. Create a new folder inside our site's /App_Plugins folder. call it CustomWelcomeDashboard

  2. Create an HTML file inside this folder called WelcomeDashboard.html. The HTML file will contain a fragment of an HTML document and does not need <html><head><body> entities.

  3. Add the following HTML to the WelcomeDashboard.html:

WelcomeDashboard.html
<div class="welcome-dashboard">
    <h1>Welcome to Umbraco</h1>
    <p>We hope you find the experience of editing your content with Umbraco enjoyable and delightful. If you discover any problems with the site please report them to the support team at <a href="mailto:">support@popularumbracopartner.com</a></p>
    <p>You can put anything here...</p>
</div>

Similar to a property editor you will now register the dashboard in a package.manifest file.

4. Add a new file inside the ~/App_Plugins/CustomWelcomeDashboard folder called package.manifest:

package.manifest
{
    "dashboards":  [
        {
            "alias": "welcomeDashboard",
            "view":  "/App_Plugins/CustomWelcomeDashboard/WelcomeDashboard.html",
            "sections":  [ "content" ],
            "weight": -10,
            "access": [
                { "deny": "translator" },
                { "grant": "admin" }
            ]
        }
    ]
}

The above configuration is effectively saying:

Add a tab called 'WelcomeDashboard' to the 'Content' section of the Umbraco site, use the WelcomeDashboard.html as the content (view) of the dashboard and don't allow 'translators', but do allow 'admins' to see it.

The order in which the tab will appear in the Umbraco Backoffice depends on its weight. To make our Custom Welcome message the first Tab the editors see, make sure the weight is less than the default dashboards. Read more about the default weights.

You can specify multiple controls to appear on a particular tab and multiple tabs in a particular section.

Step 2: Add Language Keys

After registering your dashboard, it will appear in the backoffice - however, it will have its dashboard alias [WelcomeDashboard] wrapped in square brackets. This is because it is missing a language key. The language key allows people to provide a translation of the dashboard name in multilingual environments. To remove the square brackets - add a language key:

  1. Create a Lang folder in your custom dashboard folder

  2. Create a package-specific language file: ~/App_Plugins/CustomWelcomeDashboard/Lang/en-US.xml

The App_Plugins version of the Lang directory is case-sensitive on Linux systems, so make sure that it starts with a capital L.

en-US.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language>
  <area alias="dashboardTabs">
    <key alias="welcomeDashboard">Welcome</key>
  </area>
</language>

Read more about language files

This is how our dashboard looks so far:

We can apply the same workflow to elements inside the dashboard, not only the name/alias.

3. Extend the translation file xml with the following code:

en-US.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language>
    <area alias="dashboardTabs">
        <key alias="welcomeDashboard">Welcome</key>
    </area>
    <area alias="welcomeDashboard">
        <key alias="heading">Welcome!</key>
        <key alias="bodytext">This is the Backoffice. From here, you can modify the content, media, and settings of your website.</key>
        <key alias="copyright">© Sample Company 20XX</key>
    </area>
</language>

We are adding another area tag with a few keys. we then need to add some HTML to the WelcomeDashboard.

  1. Adjust the dashboard HTML with the following code:

WelcomeDashboard.html
<div class="welcome-dashboard">
    <h1><localize key="welcomeDashboard_heading">Default heading</localize></h1>
    <p><localize key="welcomeDashboard_bodytext">Default bodytext</localize></p>
    <p><localize key="welcomeDashboard_copyright">Default copyright</localize></p>
</div>

The localize tag will be replaced with the translated keywords. We have some default text inside the tags above, which can be removed. It would usually not be visible after the translation is applied.

As for the localize tag syntax in HTML, the area alias is combined with the key alias - so if you want to translate:

en-US.xml
<localize key="welcomeDashboard_heading">Default heading</localize>

The XML for that specific key will look like this:

en-US.xml
    <area alias="welcomeDashboard">
        <key alias="heading">Welcome!</key>
    </area>

The area and key aliases are combined and an underscore is added in between.

If you don't see the brackets disappearing - you may need to restart the website.

Different languages

With the above steps completed, our dashboard is all set up to be translated across different backoffice languages.

To test it out, you can add another language XML file, like da.xml for the Danish language.

da.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language>
    <area alias="dashboardTabs">
        <key alias="welcomeDashboard">Velkommen</key>
    </area>
    <area alias="welcomeDashboard">
        <key alias="heading">Velkommen!</key>
        <key alias="bodytext">Dette er Backoffice. Herfra kan du ændre indholdet, medierne og indstillingerne på din hjemmeside.</key>
        <key alias="copyright">© Sample Selskab 20XX</key>
    </area>
</language>

The backoffice language can be changed in the Users section if you wish to test out the translations.

Step 3: Adding style

Dashboards can be styled with CSS, however, there are a couple more steps to do to be able to apply a custom stylesheet.

Inside the package.manifest we add a bit of JSON to describe the dashboard's required JavaScript and stylesheet resources:

  1. Add the following JSON to the package.manifest file:

package.manifest
{
    "dashboards":  [
        {
            "alias": "welcomeDashboard",
            "view":  "/App_Plugins/CustomWelcomeDashboard/WelcomeDashboard.html",
            "sections":  [ "content" ],
            "weight": -10,
            "access": [
                { "deny": "translator" },
                { "grant": "admin" }
            ]
        }
    ],
    "javascript": [
        /*javascript files listed here*/
    ],
    "css": [
        "~/App_Plugins/CustomWelcomeDashboard/customwelcomedashboard.css"
    ]
}
  1. Create a stylesheet in our CustomWelcomeDashboard folder called customwelcomedashboard.css, and add some style:

CustomWelcomeDashboard.csss
.welcome-dashboard h1 {
    font-size:4em;
    color:purple;
}

The stylesheet will be loaded and applied to our dashboard. Add images and HTML markup as required.

One caveat is that the package.manifest file is loaded into memory when Umbraco starts up. If you are adding a new stylesheet or JavaScript file you will need to start and stop your application for it to be loaded.

For version 9 and above

If the title doesn't change color, Smidge may be caching the CSS and JavaScript. To clear the cache and get it to load in the new JavaScript and CSS, you can configure the Runtime minification settings in the appsettings.json file. When you reload the page, you'll see the colorful title.

Smidge with RunTimeMinification setting is scheduled for removal on Umbraco 14. You can install the package separately if needed.

Hopefully, now you can see the potential of what you can provide to an editor as a basic welcome dashboard.

Step 4: Adding functionality

We can add functionality to the dashboard by associating an AngularJS controller with the HTML view.

  1. Add a new file to the CustomWelcomeDashboard folder called customwelcomedashboard.controller.js where our controller code will live.

  2. Register the AngularJS controller to the Umbraco Angular module:

customwelcomedashboard.controller.js
angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope) {
    var vm = this;
    alert("hello world");
});
  1. Update the outer div to wire up the controller to the view In the HTML view:

WelcomeDashboard.html
<div class="welcome-dashboard" ng-controller="CustomWelcomeDashboardController as vm">

The use of vm (short for view model) is to enable communication between the view and the controller.

  1. Update the package.manifest file to load the additional controller JavaScript file when the dashboard is displayed:

package.manifest
{
    "dashboards":  [
        {
            "alias": "welcomeDashboard",
            "view":  "/App_Plugins/CustomWelcomeDashboard/WelcomeDashboard.html",
            "sections":  [ "content" ],
            "weight": -10,
            "access": [
                { "deny": "translator" },
                { "grant": "admin" }
            ]
        }
    ],
    "javascript": [
        "~/App_Plugins/CustomWelcomeDashboard/customwelcomedashboard.controller.js"
    ],
    "css": [
        "~/App_Plugins/CustomWelcomeDashboard/customwelcomedashboard.css"
    ]
}

Once done, we should receive the 'Hello world' alert every time the dashboard is reloaded in the content section.

Step 5: Using Umbraco Angular Services and Directives

Umbraco has a fine selection of angular directives, resources, and services that you can use in your custom property editors and dashboards.

The details are in the Backoffice UI. For this example, it would be nice to welcome the editor by name. To achieve this we can use the userService to customize our dashboard welcome message and increase friendliness.

  1. Inject the userService into our AngularJS controller:

customwelcomedashboard.controller.js
angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope,userService) {
  1. Use the userService's promise based getCurrentUser() method to get the details of the currently logged-in user:

customwelcomedashboard.controller.js
angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService) {
    var vm = this;
    vm.UserName = "guest";

    var user = userService.getCurrentUser().then(function(user) {
        console.log(user);
        vm.UserName = user.name;
    });
});

Notice you can use console.log() to write out to the browser console window what is being returned by the promise. This helps to debug, but also understand what properties are available to use.

  1. Update the view to incorporate the current user's name in our Welcome Message:

WelcomeDashboard.html
<h1><localize key="welcomeDashboard_heading">Default heading</localize> {{vm.UserName}}</h1>

Add a list of edited articles

An editor may find it useful to see a list of articles they have been editing along with a link to load and continue editing. This could be instead of having to remember and find the item again in the Umbraco Content Tree.

We can make use of Umbraco's Angular resource for retrieving audit log information.

We add logResource to the method and use the getPagedUserLog method to return a list of activities the current user has performed recently.

  1. Inject the logResource into our controller:

customwelcomedashboard.controller.js
angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource) {
  1. Add a property on our ViewModel to store the log information:

customwelcomedashboard.controller.js
vm.UserLogHistory = []; 
  1. Add to our WelcomeDashboard.html View some markup using angular's ng-repeat to display a list of these log entries:

WelcomeDashboard.html
<h2>We know what you edited last week...</h2>
<ul>
    <li ng-repeat="logEntry in vm.UserLogHistory.items">{{logEntry.nodeId}} - {{logEntry.logType}} - {{logEntry.timestamp  | date:'medium'}}</li>
</ul>
  1. Populate the array of entries using the logResource In our controller.

The getPagedUserLog method expects to receive a JSON object containing information to filter the log by:

customwelcomedashboard.controller.js
var userLogOptions = {
    pageSize: 10,
    pageNumber: 1,
    orderDirection: "Descending",
    sinceDate: new Date(2018, 0, 1)
};

These options should retrieve the last ten activities for the current user in descending order since the start of 2018.

  1. Pass the options into the getPagedUserLog like so:

customwelcomedashboard.controller.js
logResource.getPagedUserLog(userLogOptions)
    .then(function (response) {
        console.log(response);
        vm.UserLogHistory = response;
    });

Take a look at the output of console.log of the response in your browser to see the kind of information retrieved from the log:

{pageNumber: 1, pageSize: 10, totalPages: 1, totalItems: 1, items: Array(1)}
    items: Array(1)
        0:
            $$hashKey: "object:1289"
            comment: "Published languages: English (United States)"
            entityType: "Document"
            logType: "PublishVariant"
            nodeId: 1055
            parameters: "English (United States)"
            timestamp: "2019-10-10T14:49:55.223Z"
            userAvatars: []
            userId: 1
            userName: "Jakob N"
    pageNumber: 1
    pageSize: 10
    totalItems: 1
    totalPages: 1

It's nearly all we need but missing information about the item that was saved and published.

We can use the entityResource, another Umbraco Angular resource to enable us to retrieve more information about an entity given its id.

  1. Inject the following code into our angular controller:

customwelcomedashboard.controller.js
angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) {

We need to loop through the log items from the logResource. Since this includes everything, we need to filter out activities we're not interested in eg, Macro Saves, or DocType Saves. Generally, we need the entry in the log to have a nodeId, a logType of 'save' and an entity type of Media or Content.

The entityResource has a getById method that accepts the ID of the item and the entity type to retrieve useful information about the entity. For example, its Name and Icon.

The getById method is supported on the following entity types:

  • Document (content)

  • Media

  • Member Type

  • Member Group

  • Media Type

  • Document Type

  • Member

  • Data Types

This needs to be defined before we loop through the entries.

Putting this together it will look like this:

customwelcomedashboard.controller.js
  logResource.getPagedUserLog(userLogOptions)
        .then(function (response) {
            console.log(response);
            vm.UserLogHistory = response;

            // define the entity types that we care about, in this case only content and media
            var supportedEntityTypes = ["Document", "Media"];

            // define an empty array "nodes we know about"
            var nodesWeKnowAbout = [];

            // define an empty array "filtered log entries"
            var filteredLogEntries = [];

            // loop through the entries in the User Log History
            angular.forEach(response.items, function (item) {

              // if the item is already in our "nodes we know about" array, skip to the next log entry
              if (nodesWeKnowAbout.includes(item.nodeId)) {
                return;
              }

              // if the log entry is not for an entity type that we care about, skip to the next log entry
              if (!supportedEntityTypes.includes(item.entityType)) {
                return;
              }

              // if the user did not save or publish, skip to the next log entry
              if (item.logType !== "Save" && item.logType !== "Publish") {
                return;
              }

              // if the item does not have a valid nodeId, skip to the next log entry
              if (item.nodeId < 0) {
                return;
              }

              // now, push the item's nodeId to our "nodes we know about" array
              nodesWeKnowAbout.push(item.nodeId);

              // use entityResource to retrieve details of the content/media item
              var ent = entityResource.getById(item.nodeId, item.entityType).then(function (ent) {
                  console.log(ent);
                  item.Content = ent;
              });

              // get the edit url
              if (item.entityType === "Document") {
                  item.editUrl = "content/content/edit/" + item.nodeId;
              }
              if (item.entityType === "Media") {
                  item.editUrl = "media/media/edit/" + item.nodeId;
              }

              // push the item to our "filtered log entries" array
              filteredLogEntries.push(item);

            // end of loop
            });

            // populate the view with our "filtered log entries" array
            vm.UserLogHistory.items = filteredLogEntries;

        // end of function
        });
  1. Update the view to use the additional retrieved entity information:

WelcomeDashboard.html
<h2>We know what you edited last week...</h2>
<ul class="unstyled">
    <li ng-repeat="logEntry in vm.UserLogHistory.items">
        <i class="{{logEntry.Content.icon}}"></i> <a href="/Umbraco/#/{{logEntry.editUrl}}">{{logEntry.Content.name}} <span ng-if="logEntry.comment">- {{logEntry.comment}}</span></a> - <span class="text-muted">(Edited on: {{logEntry.timestamp  | date:'medium'}})</span>
    </li>
</ul>

We now have a list of recently saved content and media on our Custom Dashboard:

The URL /umbraco/#/content/content/edit/1234 is the path to open up a particular entity (with id 1234) ready for editing.

The logResource has a few bugs prior to version 8.1.4. If you are on a lower version this may not give the expected result.

Creating a shortcut for creating new content

A key user journeys an editor will make in the backoffice is to create content. If it is a person's job to create new blog entries, why not create a handy shortcut to help them achieve this common task?

We can add a shortcut to allow the users to add a blog post.

To do this we add the following code to our view:

WelcomeDashboard.html
<div>
    <a class="btn btn-primary btn-large" href="/umbraco/#/content/content/edit/1065?doctype=BlogPost&create=true">
        <i class="icon-edit"></i>
        Create New Blog Post
    </a>
</div>

1065 is the ID of our blog section and blogPost is the alias of the type of document we want to create.

At this point we are done with the tutorial, your files should contain this:

CustomWelcomeDashboardController
customwelcomedashboard.controller.js
// Some angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) {
    var vm = this;
    vm.UserName = "guest";

    var user = userService.getCurrentUser().then(function (user) {
        console.log(user);
        vm.UserLogHistory = [];
        vm.UserName = user.name;
    });
    var userLogOptions = {
        pageSize: 10,
        pageNumber: 1,
        orderDirection: "Descending",
        sinceDate: new Date(2018, 0, 1)
    };
    
  logResource.getPagedUserLog(userLogOptions)
        .then(function (response) {
            console.log(response);
            vm.UserLogHistory = response;

            // define the entity types that we care about, in this case only content and media
            var supportedEntityTypes = ["Document", "Media"];

            // define an empty array "nodes we know about"
            var nodesWeKnowAbout = [];

            // define an empty array "filtered log entries"
            var filteredLogEntries = [];

            // loop through the entries in the User Log History
            angular.forEach(response.items, function (item) {

              // if the item is already in our "nodes we know about" array, skip to the next log entry
              if (nodesWeKnowAbout.includes(item.nodeId)) {
                return;
              }

              // if the log entry is not for an entity type that we care about, skip to the next log entry
              if (!supportedEntityTypes.includes(item.entityType)) {
                return;
              }

              // if the user did not save or publish, skip to the next log entry
              if (item.logType !== "Save" && item.logType !== "Publish") {
                return;
              }

              // if the item does not have a valid nodeId, skip to the next log entry
              if (item.nodeId < 0) {
                return;
              }

              // now, push the item's nodeId to our "nodes we know about" array
              nodesWeKnowAbout.push(item.nodeId);

              // use entityResource to retrieve details of the content/media item
              var ent = entityResource.getById(item.nodeId, item.entityType).then(function (ent) {
                  console.log(ent);
                  item.Content = ent;
              });

              // get the edit url
              if (item.entityType === "Document") {
                  item.editUrl = "content/content/edit/" + item.nodeId;
              }
              if (item.entityType === "Media") {
                  item.editUrl = "media/media/edit/" + item.nodeId;
              }

              // push the item to our "filtered log entries" array
              filteredLogEntries.push(item);

            // end of loop
            });

            // populate the view with our "filtered log entries" array
            vm.UserLogHistory.items = filteredLogEntries;

        // end of function
        });

});
WelcomeDashboard.html
WelcomeDashboard.html
<div class="welcome-dashboard" ng-controller="CustomWelcomeDashboardController as vm">
    <h1><localize key="welcomeDashboard_heading">Default heading</localize> {{vm.UserName}}</h1>
    <p><localize key="welcomeDashboard_bodytext">Default bodytext</localize></p>
    <p><localize key="welcomeDashboard_copyright">Default copyright</localize></p>

    <h2>We know what you edited last week...</h2>
    <ul class="unstyled">
        <li ng-repeat="logEntry in vm.UserLogHistory.items">
            <i class="{{logEntry.Content.icon}}"></i> <a href="/Umbraco/#/{{logEntry.editUrl}}">{{logEntry.Content.name}} <span ng-if="logEntry.comment">- {{logEntry.comment}}</span></a> - <span class="text-muted">(Edited on: {{logEntry.timestamp  | date:'medium'}})</span>
        </li>
    </ul>

    <div>
        <a class="btn btn-primary btn-large" href="/umbraco/#/content/content/edit/1065?doctype=BlogPost&create=true">
            <i class="icon-edit"></i>
            Create New Blog Post
        </a>
    </div>
</div>

Extending with Custom External Data

You can create custom Angular services/resources to interact with your own serverside data using theUmbracoAuthorizedJsonController.

Have a look at the property editor tutorial step which explains how this can be done.

Going Further

With all of the steps completed, you should have a functional dashboard that will let the logged-in user see the changes they made! Hopefully, this tutorial has given you some ideas on what is possible to do when creating a dashboard.

You can also go further and extend the dashboard with UI elements from the Umbraco UI Library.

Remember to check out the Angular API docs for more info on all of the resources and services you can find for the backoffice.

For information on creating bundles of your site's CSS or JavaScript files in your code, see the section.

Bundling & Minification for JavaScript and CSS
Welcome dashboard
Dashboard with translation keys
Changing backoffice language
Dashboard in danish
Custom Dashboard Welcome Message With styles...
Custom Dashboard Welcome Message With Current User's Name
We know what you edited last week...
Handy shortcut buttons
Custom Dashboard extended with UI Library Card