Feature: document history (audit log UI) (#6388)

This commit is contained in:
shamoon
2024-04-23 08:16:28 -07:00
committed by GitHub
parent d65fcf70f3
commit 05b1ff9738
29 changed files with 773 additions and 158 deletions

View File

@@ -424,6 +424,10 @@
<context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-history/document-history.component.html</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="7991430199894172363" datatype="html">
<source>Read the documentation about this setting</source>
@@ -447,7 +451,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">322</context>
<context context-type="linenumber">333</context>
</context-group>
</trans-unit>
<trans-unit id="3768927257183755959" datatype="html">
@@ -506,7 +510,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">314</context>
<context context-type="linenumber">325</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
@@ -636,7 +640,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">331</context>
<context context-type="linenumber">342</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@@ -947,7 +951,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">32</context>
<context context-type="linenumber">33</context>
</context-group>
</trans-unit>
<trans-unit id="293524471897878391" datatype="html">
@@ -977,7 +981,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">290</context>
<context context-type="linenumber">301</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
@@ -1682,7 +1686,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">29</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="5968132631442328843" datatype="html">
@@ -2032,7 +2036,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">768</context>
<context context-type="linenumber">769</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -2075,15 +2079,15 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">770</context>
<context context-type="linenumber">771</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1052</context>
<context context-type="linenumber">1064</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1090</context>
<context context-type="linenumber">1102</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -4321,7 +4325,7 @@
<source>Inherited from group</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.ts</context>
<context context-type="linenumber">61</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit id="6418218602775540217" datatype="html">
@@ -4822,7 +4826,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">28</context>
</context-group>
</trans-unit>
<trans-unit id="2691296884221415710" datatype="html">
@@ -4849,7 +4853,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">26</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="8911158217491828773" datatype="html">
@@ -5119,7 +5123,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1108</context>
<context context-type="linenumber">1120</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context>
@@ -5174,7 +5178,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="2091353339965748767" datatype="html">
@@ -5312,103 +5316,110 @@
<context context-type="linenumber">279,282</context>
</context-group>
</trans-unit>
<trans-unit id="186236568870281953" datatype="html">
<source>History</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">290</context>
</context-group>
</trans-unit>
<trans-unit id="5129524307369213584" datatype="html">
<source>Save &amp; next</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">316</context>
<context context-type="linenumber">327</context>
</context-group>
</trans-unit>
<trans-unit id="4910102545766233758" datatype="html">
<source>Save &amp; close</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">319</context>
<context context-type="linenumber">330</context>
</context-group>
</trans-unit>
<trans-unit id="8191371354890763172" datatype="html">
<source>Enter Password</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">370</context>
<context context-type="linenumber">381</context>
</context-group>
</trans-unit>
<trans-unit id="2218903673684131427" datatype="html">
<source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">327,329</context>
<context context-type="linenumber">328,330</context>
</context-group>
</trans-unit>
<trans-unit id="3200733026060976258" datatype="html">
<source>Document changes detected</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">350</context>
<context context-type="linenumber">351</context>
</context-group>
</trans-unit>
<trans-unit id="2887155916749964" datatype="html">
<source>The version of this document in your browser session appears older than the existing version.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">352</context>
</context-group>
</trans-unit>
<trans-unit id="237142428785956348" datatype="html">
<source>Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">352</context>
<context context-type="linenumber">353</context>
</context-group>
</trans-unit>
<trans-unit id="8720977247725652816" datatype="html">
<source>Ok</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">354</context>
<context context-type="linenumber">355</context>
</context-group>
</trans-unit>
<trans-unit id="5758784066858623886" datatype="html">
<source>Error retrieving metadata</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">494</context>
<context context-type="linenumber">495</context>
</context-group>
</trans-unit>
<trans-unit id="3456881259945295697" datatype="html">
<source>Error retrieving suggestions.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">519</context>
<context context-type="linenumber">520</context>
</context-group>
</trans-unit>
<trans-unit id="8348337312757497317" datatype="html">
<source>Document saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">640</context>
<context context-type="linenumber">641</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">651</context>
<context context-type="linenumber">652</context>
</context-group>
</trans-unit>
<trans-unit id="448882439049417053" datatype="html">
<source>Error saving document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">655</context>
<context context-type="linenumber">656</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">696</context>
<context context-type="linenumber">697</context>
</context-group>
</trans-unit>
<trans-unit id="9021887951960049161" datatype="html">
<source>Confirm delete</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">723</context>
<context context-type="linenumber">724</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
@@ -5423,35 +5434,35 @@
<source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">724</context>
<context context-type="linenumber">725</context>
</context-group>
</trans-unit>
<trans-unit id="6691075929777935948" datatype="html">
<source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">725</context>
<context context-type="linenumber">726</context>
</context-group>
</trans-unit>
<trans-unit id="719892092227206532" datatype="html">
<source>Delete document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">727</context>
<context context-type="linenumber">728</context>
</context-group>
</trans-unit>
<trans-unit id="7295637485862454066" datatype="html">
<source>Error deleting document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">746</context>
<context context-type="linenumber">747</context>
</context-group>
</trans-unit>
<trans-unit id="7362691899087997122" datatype="html">
<source>Redo OCR confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">766</context>
<context context-type="linenumber">767</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -5462,63 +5473,63 @@
<source>This operation will permanently redo OCR for this document.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">767</context>
<context context-type="linenumber">768</context>
</context-group>
</trans-unit>
<trans-unit id="5729001209753056399" datatype="html">
<source>Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">778</context>
<context context-type="linenumber">779</context>
</context-group>
</trans-unit>
<trans-unit id="4409560272830824468" datatype="html">
<source>Error executing operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">789</context>
<context context-type="linenumber">790</context>
</context-group>
</trans-unit>
<trans-unit id="4458954481601077369" datatype="html">
<source>Page Fit</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">858</context>
<context context-type="linenumber">859</context>
</context-group>
</trans-unit>
<trans-unit id="1217563727923422413" datatype="html">
<source>Split confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1050</context>
<context context-type="linenumber">1062</context>
</context-group>
</trans-unit>
<trans-unit id="2805304563009985503" datatype="html">
<source>This operation will split the selected document(s) into new documents.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1051</context>
<context context-type="linenumber">1063</context>
</context-group>
</trans-unit>
<trans-unit id="4158171846914923744" datatype="html">
<source>Split operation will begin in the background.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1066</context>
<context context-type="linenumber">1078</context>
</context-group>
</trans-unit>
<trans-unit id="3235014591864339926" datatype="html">
<source>Error executing split operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1075</context>
<context context-type="linenumber">1087</context>
</context-group>
</trans-unit>
<trans-unit id="6555329262222566158" datatype="html">
<source>Rotate confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1087</context>
<context context-type="linenumber">1099</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -5529,14 +5540,14 @@
<source>This operation will permanently rotate the original version of the current document.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1088</context>
<context context-type="linenumber">1100</context>
</context-group>
</trans-unit>
<trans-unit id="4233432423256408453" datatype="html">
<source>This will alter the original copy.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1089</context>
<context context-type="linenumber">1101</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -5547,14 +5558,21 @@
<source>Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1105</context>
<context context-type="linenumber">1117</context>
</context-group>
</trans-unit>
<trans-unit id="2962674215361798818" datatype="html">
<source>Error executing rotate operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1117</context>
<context context-type="linenumber">1129</context>
</context-group>
</trans-unit>
<trans-unit id="4958946940233632319" datatype="html">
<source>No entries found.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-history/document-history.component.html</context>
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit id="6857598786757174736" datatype="html">
@@ -6094,7 +6112,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">25</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="6954625430271090777" datatype="html">
@@ -6126,7 +6144,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">33</context>
<context context-type="linenumber">34</context>
</context-group>
</trans-unit>
<trans-unit id="3557446856808034218" datatype="html">
@@ -6176,7 +6194,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">30</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="2179847500064178686" datatype="html">
@@ -7370,6 +7388,97 @@
<context context-type="linenumber">36</context>
</context-group>
</trans-unit>
<trans-unit id="4272436583644511364" datatype="html">
<source>Just now</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="8456127468852940807" datatype="html">
<source>year ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="963494111451627204" datatype="html">
<source>years ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit id="1919405338795657780" datatype="html">
<source>month ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">47</context>
</context-group>
</trans-unit>
<trans-unit id="6041340836190906216" datatype="html">
<source>months ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">48</context>
</context-group>
</trans-unit>
<trans-unit id="4072659649620334828" datatype="html">
<source>week ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">52</context>
</context-group>
</trans-unit>
<trans-unit id="2871318661796659216" datatype="html">
<source>weeks ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">53</context>
</context-group>
</trans-unit>
<trans-unit id="1328378419272652134" datatype="html">
<source>day ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">57</context>
</context-group>
</trans-unit>
<trans-unit id="5620397708418210833" datatype="html">
<source>days ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="4259498317457105735" datatype="html">
<source>hour ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">62</context>
</context-group>
</trans-unit>
<trans-unit id="7576594819545407052" datatype="html">
<source>hours ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit id="4063456036422970205" datatype="html">
<source>minute ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">67</context>
</context-group>
</trans-unit>
<trans-unit id="6906829094715901970" datatype="html">
<source>minutes ago</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">68</context>
</context-group>
</trans-unit>
<trans-unit id="7536524521722799066" datatype="html">
<source>(no title)</source>
<context-group purpose="location">
@@ -7532,14 +7641,14 @@
<source>Modified</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">31</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="4460262093225954455" datatype="html">
<source>Search score</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">40</context>
<context context-type="linenumber">41</context>
</context-group>
<note priority="1" from="description">Score is a value returned by the full text search engine and specifies how well a result matches the given query</note>
</trans-unit>

View File

@@ -119,6 +119,7 @@ import { NgxFilesizeModule } from 'ngx-filesize'
import { RotateConfirmDialogComponent } from './components/common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
import { MergeConfirmDialogComponent } from './components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
import { SplitConfirmDialogComponent } from './components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
import { DocumentHistoryComponent } from './components/document-history/document-history.component'
import {
airplane,
archive,
@@ -472,6 +473,7 @@ function initializeApp(settings: SettingsService) {
RotateConfirmDialogComponent,
MergeConfirmDialogComponent,
SplitConfirmDialogComponent,
DocumentHistoryComponent,
],
imports: [
BrowserModule,

View File

@@ -9,17 +9,17 @@
<div class="col" i18n>Delete</div>
<div class="col" i18n>View</div>
</li>
@for (type of PermissionType | keyvalue; track type) {
<li class="list-group-item d-flex" [formGroupName]="type.key">
<div class="col-3">{{type.key}}:</div>
<div class="col form-check form-check-inline form-switch" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key)" placement="left" triggers="mouseenter:mouseleave">
<input type="checkbox" class="form-check-input" id="{{type.key}}_all" (change)="toggleAll($event, type.key)" [checked]="typesWithAllActions.has(type.key) || isInherited(type.key)" [attr.disabled]="disabled || isInherited(type.key) ? true : null">
<label class="form-check-label visually-hidden" for="{{type.key}}_all" i18n>All</label>
@for (type of allowedTypes; track type) {
<li class="list-group-item d-flex" [formGroupName]="type">
<div class="col-3">{{type}}:</div>
<div class="col form-check form-check-inline form-switch" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type)" placement="left" triggers="mouseenter:mouseleave">
<input type="checkbox" class="form-check-input" id="{{type}}_all" (change)="toggleAll($event, type)" [checked]="typesWithAllActions.has(type) || isInherited(type)" [attr.disabled]="disabled || isInherited(type) ? true : null">
<label class="form-check-label visually-hidden" for="{{type}}_all" i18n>All</label>
</div>
@for (action of PermissionAction | keyvalue; track action) {
<div class="col form-check form-check-inline" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key, action.key)" placement="left" triggers="mouseenter:mouseleave">
<input type="checkbox" class="form-check-input" id="{{type.key}}_{{action.key}}" formControlName="{{action.key}}">
<label class="form-check-label visually-hidden" for="{{type.key}}_{{action.key}}" i18n>{{action.key}}</label>
<div class="col form-check form-check-inline" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type, action.key)" placement="left" triggers="mouseenter:mouseleave">
<input type="checkbox" class="form-check-input" id="{{type}}_{{action.key}}" formControlName="{{action.key}}">
<label class="form-check-label visually-hidden" for="{{type}}_{{action.key}}" i18n>{{action.key}}</label>
</div>
}
</li>

View File

@@ -12,6 +12,9 @@ import {
} from 'src/app/services/permissions.service'
import { By } from '@angular/platform-browser'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { SettingsService } from 'src/app/services/settings.service'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { HttpClientTestingModule } from '@angular/common/http/testing'
const permissions = [
'add_document',
@@ -28,6 +31,7 @@ describe('PermissionsSelectComponent', () => {
let component: PermissionsSelectComponent
let fixture: ComponentFixture<PermissionsSelectComponent>
let permissionsChangeResult: Permissions
let settingsService: SettingsService
beforeEach(async () => {
TestBed.configureTestingModule({
@@ -38,9 +42,11 @@ describe('PermissionsSelectComponent', () => {
ReactiveFormsModule,
NgbModule,
NgxBootstrapIconsModule.pick(allIcons),
HttpClientTestingModule,
],
}).compileComponents()
settingsService = TestBed.inject(SettingsService)
fixture = TestBed.createComponent(PermissionsSelectComponent)
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
component = fixture.componentInstance
@@ -99,4 +105,11 @@ describe('PermissionsSelectComponent', () => {
const input2 = fixture.debugElement.query(By.css('input#Tag_Change'))
expect(input2.nativeElement.disabled).toBeTruthy()
})
it('should exclude history permissions if disabled', () => {
settingsService.set(SETTINGS_KEYS.AUDITLOG_ENABLED, false)
fixture = TestBed.createComponent(PermissionsSelectComponent)
component = fixture.componentInstance
expect(component.allowedTypes).not.toContain('History')
})
})

View File

@@ -12,6 +12,8 @@ import {
PermissionType,
} from 'src/app/services/permissions.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { SettingsService } from 'src/app/services/settings.service'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
@Component({
providers: [
@@ -60,15 +62,23 @@ export class PermissionsSelectComponent
inheritedWarning: string = $localize`Inherited from group`
constructor(private readonly permissionsService: PermissionsService) {
public allowedTypes = Object.keys(PermissionType)
constructor(
private readonly permissionsService: PermissionsService,
private readonly settingsService: SettingsService
) {
super()
for (const type in PermissionType) {
if (!this.settingsService.get(SETTINGS_KEYS.AUDITLOG_ENABLED)) {
this.allowedTypes.splice(this.allowedTypes.indexOf('History'), 1)
}
this.allowedTypes.forEach((type) => {
const control = new FormGroup({})
for (const action in PermissionAction) {
control.addControl(action, new FormControl(null))
}
this.form.addControl(type, control)
}
})
}
writeValue(permissions: string[]): void {
@@ -92,7 +102,7 @@ export class PermissionsSelectComponent
}
}
})
Object.keys(PermissionType).forEach((type) => {
this.allowedTypes.forEach((type) => {
if (
Object.values(this.form.get(type).value).every((val) => val == true)
) {
@@ -191,7 +201,7 @@ export class PermissionsSelectComponent
}
updateDisabledStates() {
for (const type in PermissionType) {
this.allowedTypes.forEach((type) => {
const control = this.form.get(type)
let actionControl: AbstractControl
for (const action in PermissionAction) {
@@ -200,6 +210,6 @@ export class PermissionsSelectComponent
? actionControl.disable()
: actionControl.enable()
}
}
})
}
}

View File

@@ -285,6 +285,17 @@
</li>
}
@if (historyEnabled) {
<li [ngbNavItem]="DocumentDetailNavIDs.History">
<a ngbNavLink i18n>History</a>
<ng-template ngbNavContent>
<div class="mb-3">
<pngx-document-history [documentId]="documentId"></pngx-document-history>
</div>
</ng-template>
</li>
}
@if (showPermissions) {
<li [ngbNavItem]="DocumentDetailNavIDs.Permissions">
<a ngbNavLink i18n>Permissions</a>

View File

@@ -77,6 +77,7 @@ enum DocumentDetailNavIDs {
Preview = 4,
Notes = 5,
Permissions = 6,
History = 7,
}
enum ContentRenderType {
@@ -902,6 +903,17 @@ export class DocumentDetailComponent
)
}
get historyEnabled(): boolean {
return (
this.settings.get(SETTINGS_KEYS.AUDITLOG_ENABLED) &&
this.userIsOwner &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.History
)
)
}
notesUpdated(notes: DocumentNote[]) {
this.document.notes = notes
this.openDocumentService.refreshDocument(this.documentId)

View File

@@ -0,0 +1,59 @@
@if (loading) {
<div class="d-flex">
<div class="spinner-border spinner-border-sm fw-normal" role="status"></div>
</div>
} @else {
<ul class="list-group">
@if (entries.length === 0) {
<li class="list-group-item">
<div class="d-flex justify-content-center">
<span class="fst-italic" i18n>No entries found.</span>
</div>
</li>
} @else {
@for (entry of entries; track entry.id) {
<li class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<ng-template #timestamp>
<div class="text-light">
{{ entry.timestamp | customDate:'longDate' }} {{ entry.timestamp | date:'shortTime' }}
</div>
</ng-template>
<span class="text-muted" [ngbTooltip]="timestamp">{{ entry.timestamp | customDate:'relative' }}</span>
@if (entry.actor) {
<span class="ms-3 fst-italic">{{ entry.actor.username }}</span>
} @else {
<span class="ms-3 fst-italic">System</span>
}
<span class="badge bg-secondary ms-auto" [class.bg-primary]="entry.action === AuditLogAction.Create">{{ entry.action | titlecase }}</span>
</div>
@if (entry.action === AuditLogAction.Update) {
<ul class="mt-2">
@for (change of entry.changes | keyvalue; track change.key) {
@if (change.value["type"] === 'm2m') {
<li>
<span class="fst-italic" i18n>{{ change.value["operation"] | titlecase }}</span>&nbsp;
<span class="text-light">{{ change.key | titlecase }}</span>:&nbsp;
<code class="text-primary">{{ change.value["objects"].join(', ') }}</code>
</li>
}
@else if (change.value["type"] === 'custom_field') {
<li>
<span class="text-light">{{ change.value["field"] }}</span>:&nbsp;
<code class="text-primary">{{ change.value["value"] }}</code>
</li>
}
@else {
<li>
<span class="text-light">{{ change.key | titlecase }}</span>:&nbsp;
<code class="text-primary">{{ change.value[1] }}</code>
</li>
}
}
</ul>
}
</li>
}
}
</ul>
}

View File

@@ -0,0 +1,57 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentHistoryComponent } from './document-history.component'
import { DocumentService } from 'src/app/services/rest/document.service'
import { of } from 'rxjs'
import { AuditLogAction } from 'src/app/data/auditlog-entry'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DatePipe } from '@angular/common'
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
describe('DocumentHistoryComponent', () => {
let component: DocumentHistoryComponent
let fixture: ComponentFixture<DocumentHistoryComponent>
let documentService: DocumentService
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DocumentHistoryComponent, CustomDatePipe],
providers: [DatePipe],
imports: [
HttpClientTestingModule,
NgbCollapseModule,
NgxBootstrapIconsModule.pick(allIcons),
],
}).compileComponents()
fixture = TestBed.createComponent(DocumentHistoryComponent)
documentService = TestBed.inject(DocumentService)
component = fixture.componentInstance
})
it('should get audit log entries on init', () => {
const getHistorySpy = jest.spyOn(documentService, 'getHistory')
getHistorySpy.mockReturnValue(
of([
{
id: 1,
actor: {
id: 1,
username: 'user1',
},
action: AuditLogAction.Create,
timestamp: '2021-01-01T00:00:00Z',
remote_addr: '1.2.3.4',
changes: {
title: ['old title', 'new title'],
},
},
])
)
component.documentId = 1
fixture.detectChanges()
expect(getHistorySpy).toHaveBeenCalledWith(1)
})
})

View File

@@ -0,0 +1,36 @@
import { Component, Input, OnInit } from '@angular/core'
import { AuditLogAction, AuditLogEntry } from 'src/app/data/auditlog-entry'
import { DocumentService } from 'src/app/services/rest/document.service'
@Component({
selector: 'pngx-document-history',
templateUrl: './document-history.component.html',
styleUrl: './document-history.component.scss',
})
export class DocumentHistoryComponent implements OnInit {
public AuditLogAction = AuditLogAction
private _documentId: number
@Input()
set documentId(id: number) {
this._documentId = id
this.ngOnInit()
}
public loading: boolean = true
public entries: AuditLogEntry[] = []
constructor(private documentService: DocumentService) {}
ngOnInit(): void {
if (this._documentId) {
this.loading = true
this.documentService
.getHistory(this._documentId)
.subscribe((auditLogEntries) => {
this.entries = auditLogEntries
this.loading = false
})
}
}
}

View File

@@ -0,0 +1,18 @@
import { User } from './user'
export enum AuditLogAction {
Create = 'create',
Update = 'update',
Delete = 'delete',
}
export interface AuditLogEntry {
id: number
timestamp: string
action: AuditLogAction
changes: {
[key: string]: string[]
}
remote_addr: string
actor?: User
}

View File

@@ -37,6 +37,7 @@ export const SETTINGS_KEYS = {
NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD:
'general-settings:notifications:consumer-suppress-on-dashboard',
NOTES_ENABLED: 'general-settings:notes-enabled',
AUDITLOG_ENABLED: 'general-settings:auditlog-enabled',
SLIM_SIDEBAR: 'general-settings:slim-sidebar',
UPDATE_CHECKING_ENABLED: 'general-settings:update-checking:enabled',
UPDATE_CHECKING_BACKEND_SETTING:
@@ -143,6 +144,11 @@ export const SETTINGS: UiSetting[] = [
type: 'boolean',
default: true,
},
{
key: SETTINGS_KEYS.AUDITLOG_ENABLED,
type: 'boolean',
default: true,
},
{
key: SETTINGS_KEYS.UPDATE_CHECKING_ENABLED,
type: 'boolean',

View File

@@ -30,4 +30,14 @@ describe('CustomDatePipe', () => {
)
).toEqual('2023-05-04')
})
it('should support relative date formatting', () => {
const now = new Date()
const notNow = new Date(now)
notNow.setDate(now.getDate() - 1)
expect(datePipe.transform(notNow, 'relative')).toEqual('1 day ago')
notNow.setDate(now.getDate() - 2)
expect(datePipe.transform(notNow, 'relative')).toEqual('2 days ago')
expect(datePipe.transform(now, 'relative')).toEqual('Just now')
})
})

View File

@@ -34,6 +34,51 @@ export class CustomDatePipe implements PipeTransform {
this.settings.get(SETTINGS_KEYS.DATE_LOCALE) ||
this.defaultLocale
let f = format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT)
if (format === 'relative') {
const seconds = Math.floor((+new Date() - +new Date(value)) / 1000)
if (seconds < 60) return $localize`Just now`
const intervals = {
year: {
label: $localize`year ago`,
labelPlural: $localize`years ago`,
interval: 31536000,
},
month: {
label: $localize`month ago`,
labelPlural: $localize`months ago`,
interval: 2592000,
},
week: {
label: $localize`week ago`,
labelPlural: $localize`weeks ago`,
interval: 604800,
},
day: {
label: $localize`day ago`,
labelPlural: $localize`days ago`,
interval: 86400,
},
hour: {
label: $localize`hour ago`,
labelPlural: $localize`hours ago`,
interval: 3600,
},
minute: {
label: $localize`minute ago`,
labelPlural: $localize`minutes ago`,
interval: 60,
},
}
let counter
for (const i in intervals) {
counter = Math.floor(seconds / intervals[i].interval)
if (counter > 0) {
const label =
counter > 1 ? intervals[i].labelPlural : intervals[i].label
return `${counter} ${label}`
}
}
}
if (l == 'iso-8601') {
return this.datePipe.transform(value, FORMAT_TO_ISO_FORMAT[f], timezone)
} else {

View File

@@ -19,6 +19,7 @@ export enum PermissionType {
PaperlessTask = '%s_paperlesstask',
AppConfig = '%s_applicationconfiguration',
UISettings = '%s_uisettings',
History = '%s_logentry',
Note = '%s_note',
MailAccount = '%s_mailaccount',
MailRule = '%s_mailrule',

View File

@@ -266,6 +266,13 @@ describe(`DocumentService`, () => {
)
expect(req.request.body.remove_inbox_tags).toEqual(true)
})
it('should call appropriate api endpoint for getting audit log', () => {
subscription = service.getHistory(documents[0].id).subscribe()
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}${endpoint}/${documents[0].id}/history/`
)
})
})
afterEach(() => {

View File

@@ -19,7 +19,8 @@ import {
PermissionsService,
} from '../permissions.service'
import { SettingsService } from '../settings.service'
import { SETTINGS, SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { AuditLogEntry } from 'src/app/data/auditlog-entry'
export const DOCUMENT_SORT_FIELDS = [
{ field: 'archive_serial_number', name: $localize`ASN` },
@@ -222,6 +223,10 @@ export class DocumentService extends AbstractPaperlessService<Document> {
)
}
getHistory(id: number): Observable<AuditLogEntry[]> {
return this.http.get<AuditLogEntry[]>(this.getResourceUrl(id, 'history'))
}
bulkDownload(
ids: number[],
content = 'both',

View File

@@ -47,6 +47,7 @@ describe('SettingsService', () => {
update_checking: { enabled: false, backend_setting: 'default' },
saved_views: { warn_on_unsaved_change: true },
notes_enabled: true,
auditlog_enabled: true,
tour_complete: false,
permissions: {
default_owner: null,