123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- <!DOCTYPE html>
- <html>
- <head>
- <title>Building Admin - Activity Log - QOR5 Document</title>
- <meta name='description'>
- <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
- <base href='/docs/'>
- <link href='index.css' rel='stylesheet' type='text/css'>
- <script type='text/javascript' defer src='index.js'></script>
- </head>
- <body>
- <div id='app' v-cloak>
- <div v-init-context:vars='{hideAside: false}' class='flex h-screen'>
- <div class='flex-1 flex flex-col overflow-hidden'>
- <div class='flex h-full'>
- <aside v-show='!vars.hideAside' id='menuScroller' class='flex flex-col w-80 h-full bg-gray-50 border-r border-gray-200 overflow-y-auto'>
- <div class='h-12'><search></search></div>
- <ul class='px-0 py-3 mx-0 text-base font-normal list-none text-gray-700'>
- <li class='m-0'>
- <a href='index.html' id='index.html' onclick='window.storeMenuState("index.html")' class='inline-block px-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Introduction</a>
- </li>
- <li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Getting Started</li>
- <li class='m-0'>
- <a href='getting-started/one-minute-quick-start.html' id='getting-started/one-minute-quick-start.html' onclick='window.storeMenuState("getting-started/one-minute-quick-start.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>1 Minute Quick Start</a>
- </li>
- <li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Building Admin</li>
- <li class='m-0'>
- <a href='basics/listing.html' id='basics/listing.html' onclick='window.storeMenuState("basics/listing.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Listing</a>
- </li>
- <li class='m-0'>
- <a href='basics/filter.html' id='basics/filter.html' onclick='window.storeMenuState("basics/filter.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Filters</a>
- </li>
- <li class='m-0'>
- <a href='presets-guide/editing-customizations.html' id='presets-guide/editing-customizations.html' onclick='window.storeMenuState("presets-guide/editing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Editing</a>
- </li>
- <li class='m-0'>
- <a href='basics/brand.html' id='basics/brand.html' onclick='window.storeMenuState("basics/brand.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Brand</a>
- </li>
- <li class='m-0'>
- <a href='basics/menu.html' id='basics/menu.html' onclick='window.storeMenuState("basics/menu.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Menu</a>
- </li>
- <li class='m-0'>
- <a href='presets-guide/detail-page-for-complex-object.html' id='presets-guide/detail-page-for-complex-object.html' onclick='window.storeMenuState("presets-guide/detail-page-for-complex-object.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Detailing</a>
- </li>
- <li class='m-0'>
- <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
- </li>
- <li class='m-0'>
- <a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
- </li>
- <li class='m-0'>
- <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
- </li>
- <li class='m-0'>
- <a href='presets-guide/role.html' id='presets-guide/role.html' onclick='window.storeMenuState("presets-guide/role.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Role</a>
- </li>
- <li class='m-0'>
- <a href='basics/notification-center.html' id='basics/notification-center.html' onclick='window.storeMenuState("basics/notification-center.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Notification Center</a>
- </li>
- <li class='m-0'>
- <a href='basics/shortcut.html' id='basics/shortcut.html' onclick='window.storeMenuState("basics/shortcut.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Keyboard Shortcut</a>
- </li>
- <li class='m-0'>
- <a href='basics/confirm-dialog.html' id='basics/confirm-dialog.html' onclick='window.storeMenuState("basics/confirm-dialog.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Confirm Dialog</a>
- </li>
- <li class='m-0'>
- <a href='slug.html' id='slug.html' onclick='window.storeMenuState("slug.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Slug</a>
- </li>
- <li class='m-0'>
- <a href='seo.html' id='seo.html' onclick='window.storeMenuState("seo.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>SEO</a>
- </li>
- <li class='m-0'>
- <a href='activity-log.html' id='activity-log.html' onclick='window.storeMenuState("activity-log.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-blue-500'>Activity Log</a>
- </li>
- <li class='m-0'>
- <a href='basics/worker.html' id='basics/worker.html' onclick='window.storeMenuState("basics/worker.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Worker</a>
- </li>
- <li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Web Application</li>
- <li class='m-0'>
- <a href='basics/page-func-and-event-func.html' id='basics/page-func-and-event-func.html' onclick='window.storeMenuState("basics/page-func-and-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Page Func and Event Func</a>
- </li>
- <li class='m-0'>
- <a href='advanced-functions/the-go-html-builder.html' id='advanced-functions/the-go-html-builder.html' onclick='window.storeMenuState("advanced-functions/the-go-html-builder.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>The Go HTML builder</a>
- </li>
- <li class='m-0'>
- <a href='presets-guide/its-the-whole-house.html' id='presets-guide/its-the-whole-house.html' onclick='window.storeMenuState("presets-guide/its-the-whole-house.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Not just scaffolding, it's the whole house</a>
- </li>
- <li class='m-0'>
- <a href='vuetify-components/lazy-portals.html' id='vuetify-components/lazy-portals.html' onclick='window.storeMenuState("vuetify-components/lazy-portals.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Lazy Portals</a>
- </li>
- <li class='m-0'>
- <a href='basics/layout-function-and-page-injector.html' id='basics/layout-function-and-page-injector.html' onclick='window.storeMenuState("basics/layout-function-and-page-injector.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout Function and Page Injector</a>
- </li>
- <li class='m-0'>
- <a href='basics/switch-pages-with-push-state.html' id='basics/switch-pages-with-push-state.html' onclick='window.storeMenuState("basics/switch-pages-with-push-state.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Switch Pages with Push State</a>
- </li>
- <li class='m-0'>
- <a href='basics/reload-page-with-a-flash.html' id='basics/reload-page-with-a-flash.html' onclick='window.storeMenuState("basics/reload-page-with-a-flash.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Reload Page with a Flash</a>
- </li>
- <li class='m-0'>
- <a href='basics/partial-refresh-with-portal.html' id='basics/partial-refresh-with-portal.html' onclick='window.storeMenuState("basics/partial-refresh-with-portal.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Partial Refresh with Portal</a>
- </li>
- <li class='m-0'>
- <a href='basics/manipulate-page-url-in-event-func.html' id='basics/manipulate-page-url-in-event-func.html' onclick='window.storeMenuState("basics/manipulate-page-url-in-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Manipulate Page URL in Event Func</a>
- </li>
- <li class='m-0'>
- <a href='basics/summary-of-event-response.html' id='basics/summary-of-event-response.html' onclick='window.storeMenuState("basics/summary-of-event-response.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Summary of Event Response</a>
- </li>
- <li class='m-0'>
- <a href='basics/scope-component.html' id='basics/scope-component.html' onclick='window.storeMenuState("basics/scope-component.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Scope Component</a>
- </li>
- <li class='m-0'>
- <a href='basics/event-handling.html' id='basics/event-handling.html' onclick='window.storeMenuState("basics/event-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Event Handling</a>
- </li>
- <li class='m-0'>
- <a href='basics/form-handling.html' id='basics/form-handling.html' onclick='window.storeMenuState("basics/form-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Form Handling</a>
- </li>
- <li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>UI Components</li>
- <li class='m-0'>
- <a href='vuetify-components/basic-inputs.html' id='vuetify-components/basic-inputs.html' onclick='window.storeMenuState("vuetify-components/basic-inputs.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Basic Inputs</a>
- </li>
- <li class='m-0'>
- <a href='vuetify-components/a-taste-of-using-vuetify-in-go.html' id='vuetify-components/a-taste-of-using-vuetify-in-go.html' onclick='window.storeMenuState("vuetify-components/a-taste-of-using-vuetify-in-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>A Taste of using Vuetify in Go</a>
- </li>
- <li class='m-0'>
- <a href='vuetify-components/linkage-select.html' id='vuetify-components/linkage-select.html' onclick='window.storeMenuState("vuetify-components/linkage-select.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Linkage Select</a>
- </li>
- <li class='m-0'>
- <a href='vuetify-components/auto-complete.html' id='vuetify-components/auto-complete.html' onclick='window.storeMenuState("vuetify-components/auto-complete.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Auto Complete</a>
- </li>
- <li class='m-0'>
- <a href='components-guide/composite-new-component-with-go.html' id='components-guide/composite-new-component-with-go.html' onclick='window.storeMenuState("components-guide/composite-new-component-with-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Composite new Component With Go</a>
- </li>
- <li class='m-0'>
- <a href='components-guide/integrate-a-heavy-vue-component.html' id='components-guide/integrate-a-heavy-vue-component.html' onclick='window.storeMenuState("components-guide/integrate-a-heavy-vue-component.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Integrate a heavy Vue Component</a>
- </li>
- <li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Appendix</li>
- <li class='m-0'>
- <a href='appendix/all-demo-examples.html' id='appendix/all-demo-examples.html' onclick='window.storeMenuState("appendix/all-demo-examples.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>All Demo Examples</a>
- </li>
- </ul>
- </aside>
- <main class='flex flex-col w-full bg-white overflow-x-hidden overflow-y-auto'>
- <div id='docContentBox' class='flex flex-row w-full'>
- <div class='flex flex-grow flex-col w-2/3'>
- <div class='flex flex-row'>
- <button @click='vars.hideAside = !vars.hideAside' class='w-12 h-12 p-4'>
- <div class='w-4 h-4 fill-current text-gray-300'>
- <?xml version="1.0" encoding="UTF-8"?>
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
- <g id="surface1">
- <path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2 12 L 2 11 L 14 11 L 14 12 Z M 2 8.5 L 2 7.5 L 14 7.5 L 14 8.5 Z M 2 5 L 2 4 L 14 4 L 14 5 Z M 2 5 "/>
- </g>
- </svg>
- </div>
- </button>
- </div>
- <div id='docMainBox' class='px-16 pb-12 pt-4 overflow-auto'>
- <h1 class='mb-8'>Activity Log</h1>
- <div class='border-t'><p>QOR5 provides a built-in activity module for recording model operations that may be important for admin users of CMS. These records are designed to be easily queried and audited, and the activity module supports the following features:</p>
- <ul>
- <li>Detailed change logging functionality for model data modifications.</li>
- <li>Allow certain fields to be ignored when comparing modified data, such as the update time.</li>
- <li>Customization of the diffing process for complex field types, like time.Time.</li>
- <li>Customization of the keys used to identify model data.</li>
- <li>Support both automatic and manual CRUD operation recording.</li>
- <li>Provide flexibility to customize the actions other than default CRUD.</li>
- <li>An page for querying the activity log via QOR5 admin</li>
- </ul>
- <h2><a name="initialize-the-activity-package" class="anchor" href="#initialize-the-activity-package" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
- Initialize the activity package</h2>
- <p>To initialize activity package with the default configuration, you need to pass a <code>presets.Builder</code> instance and a database instance.</p>
- <highlightjs :language='"go"' :code='"presetsBuilder := presets.New()\ndb, err := gorm.Open(sqlite.Open(\"/tmp/activity.db\"), \u0026gorm.Config{})\nif err != nil {\n\tpanic(err)\n}\nactivityBuilder := activity.New(presetsBuilder, db)"'></highlightjs>
- <p>By default, the activity package uses QOR5 login package's <code>login.UserKey</code> as the default key to fetch the current user from the context. If you want to use your own key, you can use the <code>SetCreatorContextKey</code> function.</p>
- <p>Same with above, the activity package uses the db instance that passed in during initialization to perform db operations. If you need another db to do the work, you can use <code>SetDBContextKey</code> method.</p>
- <h2><a name="register-the-models-that-require-activity-tracking" class="anchor" href="#register-the-models-that-require-activity-tracking" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
- Register the models that require activity tracking</h2>
- <p>This example demonstrates how to register <code>Product</code> into the activity. The activities on the product model will be automatically recorded when it is created, updated, or deleted.</p>
- <highlightjs :language='"go"' :code='"type Product struct {\n\tTitle string\n\tCode string\n\tPrice float64\n}\nproductModel := presetsBuilder.Model(\u0026Product{})\n\nactivityBuilder.RegisterModel(productModel).UseDefaultTab().AddKeys(\"Title\").AddIgnoredFields(\"Code\").SkipDelete()"'></highlightjs>
- <p>By default, the activity package will use the primary key as the key to indentify the current model data. You can use <code>SetKeys</code> and <code>AddKeys</code> methods to customize it.</p>
- <p>When diffing the modified data, the activity package will ignore the <code>ID</code>, <code>CreatedAt</code>, <code>UpdatedAt</code>, <code>DeletedAt</code> fields. You can either use <code>AddIgnoredFields</code> to append your own fields to the default ignored fields. Or <code>SetIgnoredFields</code> method to replace the default ignored fields.</p>
- <p>For special fields like <code>time.Time</code> or media files handled by QOR5 media_library, activity package already handled them. You can use <code>AddTypeHanders</code> method to handle your own field types.</p>
- <p>If you want to skip the automatic recording, you can use <code>SkipCreate</code>, <code>SkipUpdate</code> and <code>SkipDelete</code> methods.</p>
- <p>The Activity package allows for displaying the activities of a record on its editing page. Simply use the <code>EnableActivityInfoTab</code> method to enable this feature. Once enabled, you can customize the format of each activity's display text using the <code>SetTabHeading</code> method. Additionally, you can make each activity a link to the corresponding record using the <code>SetLink</code> method.</p>
- <h2><a name="record-the-activity-log-manually" class="anchor" href="#record-the-activity-log-manually" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
- Record the activity log manually</h2>
- <p>If you register a preset model into the activity, the activity package will automatically record the activity log for CRUD operations. However, if you need to manually record the activity log for other operations or if you want to register a non-preset model, you can use the following sample code.</p>
- <highlightjs :language='"go"' :code='"currentCtx := context.WithValue(context.Background(), activity.CreatorContextKey, \"user1\")\n\nactivityBuilder.AddRecords(\"Publish\", currentCtx, \u0026Product{Title: \"Product 1\", Code: \"P1\", Price: 100})\n\nactivityBuilder.AddRecords(\"Update Price\", currentCtx, \u0026Product{Title: \"Product 1\", Code: \"P1\", Price: 200})"'></highlightjs>
- </div>
- </div>
- </div>
- <div class='font-medium text-base hidden xl:block text-gray-600 pt-4'>
- <div class='sticky top-4 w-52'>On This Page<toc></toc></div>
- </div>
- </div>
- <search-result></search-result></main>
- </div>
- </div>
- </div>
- </div>
- </body>
- </html>
|