refs #5517 vnLog: View improved
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Juan Ferrer 2023-04-03 03:54:35 +02:00
parent 0ea30552dd
commit 5b80951e3e
9 changed files with 313 additions and 137 deletions

View File

@ -32,6 +32,7 @@ import './float-button';
import './icon-menu';
import './icon-button';
import './input-number';
import './json-value';
import './label-value';
import './range';
import './input-time';

View File

@ -0,0 +1 @@
<span></span>

View File

@ -0,0 +1,56 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
const maxStrLen = 25;
/**
* Displays pretty JSON value.
*
* @property {*} value The value
*/
export default class Controller extends Component {
get value() {
return this._value;
}
set value(value) {
this._value = value;
const span = this.element;
const formattedValue = this.formatValue(value);
span.textContent = formattedValue;
span.title = typeof value == 'string' && value.length > maxStrLen ? value : '';
span.className = `js-${value == null ? 'null' : typeof value}`;
}
formatValue(value) {
if (value == null) return '∅';
switch (typeof value) {
case 'boolean':
return value ? '✓' : '✗';
case 'string':
return value.length <= maxStrLen
? value
: value.substring(0, maxStrLen) + '...';
case 'object':
if (value instanceof Date) {
const hasZeroTime =
value.getHours() === 0 &&
value.getMinutes() === 0 &&
value.getSeconds() === 0;
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
return this.$filter('date')(value, format);
} else
return value;
default:
return value;
}
}
}
ngModule.vnComponent('vnJsonValue', {
controller: Controller,
bindings: {
value: '<?'
}
});

View File

@ -0,0 +1,21 @@
vn-json-value {
display: inline;
&.js-string {
color: #d172cc;
}
&.js-object {
/*color: #d1a572;*/
color: #d172cc;
}
&.js-number {
color: #85d0ff;
}
&.js-boolean {
color: #7dc489;
}
&.js-null {
color: #cd7c7c;
font-style: italic;
}
}

View File

@ -41,10 +41,15 @@ vn-table {
display: table-row;
height: 48px;
}
vn-thead, .vn-thead,
vn-tbody, .vn-tbody,
vn-tfoot, .vn-tfoot,
thead, tbody, tfoot {
& > thead,
& > tbody,
& > tfoot,
& > vn-thead,
& > vn-tbody,
& > vn-tfoot,
& > .vn-thead,
& > .vn-tbody,
& > .vn-tfoot {
& > * {
display: table-row;
@ -111,14 +116,14 @@ vn-table {
color: inherit;
}
}
a.vn-tbody {
& > a.vn-tbody {
&.clickable {
@extend %clickable;
}
}
vn-tbody > *,
.vn-tbody > *,
tbody > * {
& > vn-tbody > *,
& > .vn-tbody > *,
& > tbody > * {
border-bottom: $border-thin;
&:last-child {

View File

@ -9,63 +9,99 @@
limit="20"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model" class="vn-w-xl">
<vn-data-viewer model="model" class="vn-w-md">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="creationDate">Date</vn-th>
<vn-th field="userFk" shrink>User</vn-th>
<vn-th field="changedModel" ng-if="$ctrl.showModelName" shrink>Model</vn-th>
<vn-th field="action" shrink>Action</vn-th>
<vn-th field="changedModelValue" ng-if="$ctrl.showModelName">Name</vn-th>
<vn-th expand>Changes</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="log in $ctrl.logs">
<vn-td shrink-datetime>
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td>
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name || 'System' | translate}}
<table class="vn-table" model="model">
<thead>
<tr>
<th translate field="action" class="action">
Action
</th>
<th field="changedModel">
<span ng-if="$ctrl.showModelName" translate>Model</span>
</th>
<th translate field="creationDate" class="date">
Date
</th>
</tr>
</thead>
<tbody ng-repeat="log in $ctrl.logs">
<tr class="change-header">
<td class="user">
<span ng-if="::log.user.worker.id"
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)">
{{::log.user.name}}
</span>
</vn-td>
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModel}}
</vn-td>
<vn-td shrink translate>
{{::$ctrl.actionsText[log.action]}}
</vn-td>
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModelValue}}
</vn-td>
<vn-td expand>
<table class="attributes">
<thead>
<tr>
<th translate class="field">Field</th>
<th translate>Before</th>
<th translate>After</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="prop in ::log.props">
<td class="field">{{prop.name}}</td>
<td class="before">{{prop.old}}</td>
<td class="after">{{prop.new}}</td>
</tr>
</tbody>
</table>
<div ng-if="log.description != null">
{{::log.description}}
<span ng-if="::!log.user.worker.id" translate>
System
</span>
</td>
<td title="{{::log.changedModelValue}}">
<span class="model-name" ng-if="::$ctrl.showModelName">
{{::log.changedModel}}
</span>
<span class="model-value">
{{::log.changedModelValue}}
</span>
<span class="model-id">
#{{::log.changedModelId}}
</span>
</td>
<td class="date">
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}
</td>
</tr>
<tr class="change-detail">
<td class="action">
<span class="chip {{::$ctrl.actionsClass[log.action]}}" translate>
{{::$ctrl.actionsText[log.action]}}
</span>
</td>
<td colspan="2">
<div vn-id="changes" class="changes {{::log.props.length ? 'props' : 'no-props'}}">
<vn-icon icon="visibility"
class="expand-button"
ng-click="$ctrl.toggleAttributes(log, changes, true)">
</vn-icon>
<vn-icon icon="visibility_off"
class="shrink-button"
ng-click="$ctrl.toggleAttributes(log, changes, false)">
</vn-icon>
<div class="changes-wrapper">
<span ng-if="::log.props.length"
class="attributes">
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
class="basic-json">
<span class="json-field">{{::prop.name}}:</span>
<vn-json-value value="$ctrl.mainVal(prop, log.action)"></vn-json-value><span ng-if="::!$last">,</span>
</span>
<div ng-if="log.expand"
class="expanded-json">
<div ng-repeat="prop in ::log.props">
<span class="json-field">{{::prop.name}}:</span>
<vn-json-value value="$ctrl.mainVal(prop, log.action)"></vn-json-value>
<span ng-if="::log.action == 'update'">
<vn-json-value value="prop.old"></vn-json-value>
</span>
</div>
</div>
</span>
<span ng-if="::!log.props.length"
class="description">
{{::log.description}}
</span>
<span ng-if="!log.description && !log.props.length"
class="no-changes"
translate>
No changes
</span>
</div>
</div>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</td>
</tr>
</tbody>
</table>
<vn-pagination model="model"></vn-pagination>
</vn-card>
</vn-data-viewer>

View File

@ -13,6 +13,12 @@ export default class Controller extends Section {
delete: 'Deletes',
select: 'Views'
};
this.actionsClass = {
insert: 'success',
update: 'warning',
delete: 'alert',
select: 'notice'
};
this.filter = {
include: [{
relation: 'user',
@ -50,8 +56,8 @@ export default class Controller extends Section {
for (const prop of props) {
log.props.push({
name: locale[prop] || prop,
old: this.formatValue(oldValues[prop]),
new: this.formatValue(newValues[prop])
old: this.castValue(oldValues[prop]),
new: this.castValue(newValues[prop])
});
}
}
@ -61,31 +67,19 @@ export default class Controller extends Section {
return !(this.changedModel && this.changedModelId);
}
formatValue(value) {
let type = typeof value;
castValue(value) {
return typeof value === 'string' && validDate.test(value)
? new Date(value)
: value;
}
if (type === 'string' && validDate.test(value)) {
value = new Date(value);
type = typeof value;
}
mainVal(prop, action) {
return action == 'delete' ? prop.old : prop.new;
}
switch (type) {
case 'boolean':
return value ? '✓' : '✗';
case 'object':
if (value instanceof Date) {
const hasZeroTime =
value.getHours() === 0 &&
value.getMinutes() === 0 &&
value.getSeconds() === 0;
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
return this.$filter('date')(value, format);
}
else
return value;
default:
return value;
}
toggleAttributes(log, changesEl, force) {
log.expand = force;
changesEl.classList.toggle('expanded', force);
}
showWorkerDescriptor(event, workerId) {

View File

@ -13,3 +13,4 @@ Views: Visualiza
System: Sistema
note: nota
Changes: Cambios
No changes: No hay cambios

View File

@ -1,66 +1,127 @@
@import "variables";
vn-log {
vn-td {
vertical-align: initial !important;
.vn-table {
table-layout: fixed;
& > thead,
& > tbody {
& > tr {
td, th {
&:first-child {
padding-left: 16px;
}
&:last-child {
padding-right: 16px;
}
}
}
}
& > thead > tr > th {
max-width: initial;
}
& > tbody {
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
&:last-child {
border-bottom: none;
}
& > tr {
border-bottom: none;
height: initial;
& > td {
padding-top: 16px;
padding-bottom: 16px;
&.action > .chip {
display: inline-block;
}
}
&.change-header > td {
padding-bottom: 0;
}
&.change-detail > td {
padding-top: 6px;
vertical-align: top;
}
}
}
th, td {
&.action,
&.user {
width: 90px;
}
&.date {
width: 120px;
text-align: right;
}
}
}
.model-value {
font-style: italic;
color: #c7bd2b;
}
.model-id {
color: $color-font-secondary;
font-size: .9em;
}
.changes {
display: none;
}
.label {
overflow: hidden;
background-color: rgba(255, 255, 255, .05);
border-radius: 4px;
color: $color-font-secondary;
}
.value {
color: $color-font;
}
transition: max-height 150ms ease-in-out;
max-height: 28px;
position: relative;
@media screen and (max-width: 1570px) {
vn-table .expendable {
& > .expand-button,
& > .shrink-button {
display: none;
}
.changes {
padding-top: 10px;
display: block;
&.props {
padding-right: 24px;
& > .expand-button,
& > .shrink-button {
position: absolute;
top: 6px;
right: 8px;
font-size: inherit;
float: right;
cursor: pointer;
}
& > .expand-button {
display: block;
}
&.expanded {
max-height: 500px;
padding-right: 0;
& > .changes-wrapper {
text-overflow: initial;
white-space: initial;
}
& > .shrink-button {
display: block;
}
& > .expand-button {
display: none;
}
}
}
}
.attributes {
width: 100%;
& > .changes-wrapper {
padding: 4px 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
tr {
height: 10px;
& > td {
padding: 2px;
& > .no-changes {
font-style: italic;
}
& > td.field,
& > th.field {
width: 20%;
color: gray;
}
& > td.before,
& > th.before,
& > td.after,
& > th.after {
width: 40%;
white-space: pre-line;
.json-field {
text-transform: capitalize;
}
}
}
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
max-width: 400px;
text-overflow: ellipsis;
display: inline-block;
}
.no-ellipsize,
[no-ellipsize] {
text-overflow: '';
white-space: normal;
overflow: auto;
}
.alignSpan {
overflow: hidden;
display: inline-block;
}