• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / gui / 919001084

pending completion
919001084

Pull #3839

gitlab-ci

mzedel
revert: "chore: bump node from 20.2.0-alpine to 20.3.1-alpine"

This reverts commit cbfcd7663.

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3839: Combined PRs

4399 of 6397 branches covered (68.77%)

8302 of 10074 relevant lines covered (82.41%)

162.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.34
/src/js/components/auditlogs/auditlogslist.js
1
// Copyright 2020 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14
import React, { useMemo } from 'react';
15
import { Link } from 'react-router-dom';
16

17
import { ArrowRightAlt as ArrowRightAltIcon, Sort as SortIcon } from '@mui/icons-material';
18

19
import { SORTING_OPTIONS, canAccess } from '../../constants/appConstants';
20
import { DEPLOYMENT_ROUTES } from '../../constants/deploymentConstants';
21
import Loader from '../common/loader';
22
import Pagination from '../common/pagination';
23
import Time from '../common/time';
24
import EventDetailsDrawer from './eventdetailsdrawer';
25

26
export const defaultRowsPerPage = 20;
5✔
27

28
const ArtifactLink = ({ item }) => <Link to={`/releases/${item.object.artifact.name}`}>View artifact</Link>;
5✔
29
const DeploymentLink = ({ item }) => <Link to={`${DEPLOYMENT_ROUTES.finished.route}?open=true&id=${item.object.id}`}>View deployment</Link>;
11✔
30
const DeviceLink = ({ item }) => <Link to={`/devices?id=${item.object.id}`}>View device</Link>;
5✔
31
const DeviceRejectedLink = ({ item }) => <Link to={`/devices/rejected?id=${item.object.id}`}>View device</Link>;
5✔
32
const TerminalSessionLink = () => <a>View session log</a>;
11✔
33
const UserChange = ({ item: { change = '-' } }) => {
5!
34
  const formatChange = change => {
11✔
35
    const diff = change.split(/@@.*@@/);
11✔
36
    return diff.length > 1 ? diff[1].trim() : diff;
11!
37
  };
38
  return (
11✔
39
    <div className="capitalized" style={{ alignItems: 'flex-start', whiteSpace: 'pre-line' }}>
40
      {formatChange(change)}
41
    </div>
42
  );
43
};
44

45
const fallbackFormatter = data => {
5✔
46
  let result = '';
×
47
  try {
×
48
    result = JSON.stringify(data);
×
49
  } catch (error) {
50
    console.log(error);
×
51
  }
52
  return result;
×
53
};
54

55
const defaultAccess = canAccess;
5✔
56
const changeMap = {
5✔
57
  default: { component: 'div', actionFormatter: fallbackFormatter, title: 'defaultTitle', accessCheck: defaultAccess },
58
  artifact: { actionFormatter: data => decodeURIComponent(data.artifact.name), component: ArtifactLink, accessCheck: ({ canReadReleases }) => canReadReleases },
×
59
  deployment: {
60
    actionFormatter: data => decodeURIComponent(data.deployment.name),
11✔
61
    component: DeploymentLink,
62
    accessCheck: ({ canReadDeployments }) => canReadDeployments
11✔
63
  },
64
  deviceDecommissioned: { actionFormatter: data => decodeURIComponent(data.id), component: 'div', accessCheck: defaultAccess },
×
65
  deviceRejected: { actionFormatter: data => decodeURIComponent(data.id), component: DeviceRejectedLink, accessCheck: ({ canReadDevices }) => canReadDevices },
×
66
  deviceGeneral: { actionFormatter: data => decodeURIComponent(data.id), component: DeviceLink, accessCheck: ({ canReadDevices }) => canReadDevices },
×
67
  deviceTerminalSession: { actionFormatter: data => decodeURIComponent(data.id), component: TerminalSessionLink, accessCheck: defaultAccess },
11✔
68
  user: { component: UserChange, actionFormatter: data => data.user.email, accessCheck: defaultAccess }
11✔
69
};
70

71
const mapChangeToContent = item => {
5✔
72
  let content = changeMap[item.object.type];
66✔
73
  if (content) {
66✔
74
    return content;
44✔
75
  } else if (item.object.type === 'device' && item.action.includes('terminal')) {
22!
76
    content = changeMap.deviceTerminalSession;
22✔
77
  } else if (item.object.type === 'device' && item.action.includes('reject')) {
×
78
    content = changeMap.deviceRejected;
×
79
  } else if (item.object.type === 'device' && item.action.includes('decommission')) {
×
80
    content = changeMap.deviceDecommissioned;
×
81
  } else if (item.object.type === 'device') {
×
82
    content = changeMap.deviceGeneral;
×
83
  } else {
84
    content = changeMap.default;
×
85
  }
86
  return content;
22✔
87
};
88

89
const actorMap = {
5✔
90
  user: 'email',
91
  device: 'id'
92
};
93

94
const UserDescriptor = (item, index) => <div key={`${item.time}-${index} `}>{item.actor[actorMap[item.actor.type]]}</div>;
33✔
95
const ActionDescriptor = (item, index) => (
5✔
96
  <div className="uppercased" key={`${item.time}-${index}`}>
33✔
97
    {item.action}
98
  </div>
99
);
100
const TypeDescriptor = (item, index) => (
5✔
101
  <div className="capitalized" key={`${item.time}-${index}`}>
33✔
102
    {item.object.type}
103
  </div>
104
);
105
const ChangeDescriptor = (item, index) => <div key={`${item.time}-${index}`}>{mapChangeToContent(item).actionFormatter(item.object)}</div>;
33✔
106
const ChangeDetailsDescriptor = (item, index, userCapabilities) => {
5✔
107
  const { component: Comp, accessCheck } = mapChangeToContent(item);
33✔
108
  const key = `${item.time}-${index}`;
33✔
109
  return accessCheck(userCapabilities) ? <Comp key={key} item={item} /> : <div key={key} />;
33!
110
};
111
const TimeWrapper = (item, index) => <Time key={`${item.time}-${index}`} value={item.time} />;
33✔
112

113
const auditLogColumns = [
5✔
114
  { title: 'User', sortable: false, render: UserDescriptor },
115
  { title: 'Action', sortable: false, render: ActionDescriptor },
116
  { title: 'Type', sortable: false, render: TypeDescriptor },
117
  { title: 'Changed', sortable: false, render: ChangeDescriptor },
118
  { title: 'More details', sortable: false, render: ChangeDetailsDescriptor },
119
  { title: 'Time', sortable: true, render: TimeWrapper }
120
];
121

122
export const AuditLogsList = ({ items, loading, onChangePage, onChangeRowsPerPage, onChangeSorting, selectionState, setAuditlogsState, userCapabilities }) => {
5✔
123
  const { page, perPage, selectedId, sort = {}, total: count } = selectionState;
11!
124

125
  const onIssueSelection = selectedIssue =>
11✔
126
    setAuditlogsState({ selectedId: selectedIssue ? btoa(`${selectedIssue.action}|${selectedIssue.time}`) : undefined });
1!
127

128
  const eventItem = useMemo(() => {
11✔
129
    if (!selectedId) {
4!
130
      return;
4✔
131
    }
132
    const [eventAction, eventTime] = atob(selectedId).split('|');
×
133
    return items.find(item => item.action === eventAction && item.time === eventTime);
×
134
  }, [items, selectedId]);
135

136
  return (
11✔
137
    !!items.length && (
22✔
138
      <div className="fadeIn deploy-table-contain auditlogs-list">
139
        <div className="auditlogs-list-item auditlogs-list-item-header muted">
140
          {auditLogColumns.map((column, index) => (
141
            <div
66✔
142
              className="columnHeader"
143
              key={`columnHeader-${index}`}
144
              onClick={() => (column.sortable ? onChangeSorting() : null)}
×
145
              style={column.sortable ? {} : { cursor: 'initial' }}
66✔
146
            >
147
              {column.title}
148
              {column.sortable ? <SortIcon className={`sortIcon selected ${(sort.direction === SORTING_OPTIONS.desc).toString()}`} /> : null}
66✔
149
            </div>
150
          ))}
151
          <div />
152
        </div>
153
        <div className="auditlogs-list">
154
          {items.map(item => {
155
            const allowsExpansion = !!item.change || item.action.includes('terminal') || item.action.includes('portforward');
33!
156
            return (
33✔
157
              <div
158
                className={`auditlogs-list-item ${allowsExpansion ? 'clickable' : ''}`}
33!
159
                key={`event-${item.time}`}
160
                onClick={() => onIssueSelection(allowsExpansion ? item : undefined)}
1!
161
              >
162
                {auditLogColumns.map((column, index) => column.render(item, index, userCapabilities))}
198✔
163
                {allowsExpansion ? (
33!
164
                  <div className="uppercased link-color bold">
165
                    view details <ArrowRightAltIcon />
166
                  </div>
167
                ) : (
168
                  <div />
169
                )}
170
              </div>
171
            );
172
          })}
173
        </div>
174
        <div className="flexbox margin-top">
175
          <Pagination
176
            className="margin-top-none"
177
            count={count}
178
            rowsPerPage={perPage}
179
            onChangeRowsPerPage={onChangeRowsPerPage}
180
            page={page}
181
            onChangePage={onChangePage}
182
          />
183
          <Loader show={loading} small />
184
        </div>
185
        <EventDetailsDrawer eventItem={eventItem} open={Boolean(eventItem)} onClose={() => onIssueSelection()} />
×
186
      </div>
187
    )
188
  );
189
};
190

191
export default AuditLogsList;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc