blob: 7e453a2851594ca066947662a07adec00bdcd17c [file] [log] [blame]
Adam Pardylf1af74f2019-09-16 12:18:15 +02001<!-- Copyright (C) 2019 The Android Open Source Project
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-->
15<template>
16 <md-card-content class="container">
Pablo Gamito677dbec2020-06-05 16:51:19 +010017 <div class="navigation">
Nataniel Borgesf230a132021-07-28 07:13:45 +000018 <md-content
19 md-tag="md-toolbar"
20 md-elevation="0"
21 class="card-toolbar md-transparent md-dense"
Pablo Gamito7099c952020-06-10 17:31:28 +010022 >
Nataniel Borgesf230a132021-07-28 07:13:45 +000023 <h2 class="md-title" style="flex: 1">Log View</h2>
24 <md-button
25 class="md-dense md-primary"
26 @click.native="scrollToRow(lastOccuredVisibleIndex)"
27 >
28 Jump to latest entry
29 </md-button>
30 <md-button
31 class="md-icon-button" :class="{'md-primary': pinnedToLatest}"
32 @click.native="togglePin"
33 >
34 <md-icon>push_pin</md-icon>
35 <md-tooltip md-direction="top" v-if="pinnedToLatest">
36 Unpin to latest message
37 </md-tooltip>
38 <md-tooltip md-direction="top" v-else>
39 Pin to latest message
40 </md-tooltip>
41 </md-button>
42 </md-content>
Pablo Gamito677dbec2020-06-05 16:51:19 +010043 </div>
44
Pablo Gamito84bd8ad2020-06-01 13:46:41 +010045 <div class="filters">
46 <md-field>
Pablo Gamito42cef4b2020-06-26 16:55:33 +010047 <label>Log Levels</label>
48 <md-select v-model="selectedLogLevels" multiple>
49 <md-option v-for="level in logLevels" :value="level">{{ level }}</md-option>
50 </md-select>
51 </md-field>
52
53 <md-field>
Pablo Gamito84bd8ad2020-06-01 13:46:41 +010054 <label>Tags</label>
55 <md-select v-model="selectedTags" multiple>
56 <md-option v-for="tag in tags" :value="tag">{{ tag }}</md-option>
57 </md-select>
58 </md-field>
59
60 <md-autocomplete v-model="selectedSourceFile" :md-options="sourceFiles">
61 <label>Source file</label>
62
63 <template slot="md-autocomplete-item" slot-scope="{ item, term }">
64 <md-highlight-text :md-term="term">{{ item }}</md-highlight-text>
65 </template>
66
67 <template slot="md-autocomplete-empty" slot-scope="{ term }">
68 No source file matching "{{ term }}" was found.
69 </template>
70 </md-autocomplete>
71
72 <md-field class="search-message-field" md-clearable>
73 <md-input placeholder="Search messages..." v-model="searchInput"></md-input>
74 </md-field>
75 </div>
76
Pablo Gamitof0031c42020-06-26 15:35:42 +010077 <div v-if="processedData.length > 0" style="overflow-y: auto;">
78 <virtual-list style="height: 600px; overflow-y: auto;"
79 :data-key="'uid'"
80 :data-sources="processedData"
81 :data-component="logEntryComponent"
82 ref="loglist"
83 />
84 </div>
85 <div class="no-logs-message" v-else>
86 <md-icon>error_outline</md-icon>
87 <span class="message">No logs founds...</span>
88 </div>
Adam Pardylf1af74f2019-09-16 12:18:15 +020089 </md-card-content>
90</template>
91<script>
Pablo Gamito7099c952020-06-10 17:31:28 +010092import { findLastMatchingSorted } from './utils/utils.js';
Pablo Gamito42cef4b2020-06-26 16:55:33 +010093import { logLevel } from './utils/consts';
Pablo Gamito7099c952020-06-10 17:31:28 +010094import LogEntryComponent from './LogEntry.vue';
95import VirtualList from '../libs/virtualList/VirtualList';
Pablo Gamito84bd8ad2020-06-01 13:46:41 +010096
Adam Pardylf1af74f2019-09-16 12:18:15 +020097export default {
98 name: 'logview',
99 data() {
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100100 const data = this.file.data;
101
102 const tags = new Set();
103 const sourceFiles = new Set();
104 for (const line of data) {
105 tags.add(line.tag);
106 sourceFiles.add(line.at);
107 }
108
Pablo Gamito7099c952020-06-10 17:31:28 +0100109 data.forEach((entry, index) => entry.index = index);
110
Pablo Gamito42cef4b2020-06-26 16:55:33 +0100111 const logLevels = Object.values(logLevel);
112
Adam Pardylf1af74f2019-09-16 12:18:15 +0200113 return {
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100114 data,
Adam Pardylf1af74f2019-09-16 12:18:15 +0200115 isSelected: false,
Pablo Gamito7099c952020-06-10 17:31:28 +0100116 prevLastOccuredIndex: -1,
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100117 lastOccuredIndex: 0,
Pablo Gamito42cef4b2020-06-26 16:55:33 +0100118 selectedTags: [],
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100119 selectedSourceFile: null,
120 searchInput: null,
Pablo Gamito7099c952020-06-10 17:31:28 +0100121 sourceFiles: Object.freeze(Array.from(sourceFiles)),
122 tags: Object.freeze(Array.from(tags)),
Pablo Gamito677dbec2020-06-05 16:51:19 +0100123 pinnedToLatest: true,
Pablo Gamito7099c952020-06-10 17:31:28 +0100124 logEntryComponent: LogEntryComponent,
Pablo Gamito42cef4b2020-06-26 16:55:33 +0100125 logLevels,
126 selectedLogLevels: [],
Adam Pardylf1af74f2019-09-16 12:18:15 +0200127 }
128 },
129 methods: {
130 arrowUp() {
131 this.isSelected = !this.isSelected;
132 return !this.isSelected;
133 },
134 arrowDown() {
135 this.isSelected = !this.isSelected;
136 return !this.isSelected;
Pablo Gamitof2f36d32020-05-29 14:19:23 +0100137 },
138 getRowEl(idx) {
139 return this.$refs.tableBody.querySelectorAll('tr')[idx];
140 },
Pablo Gamito677dbec2020-06-05 16:51:19 +0100141 togglePin() {
142 this.pinnedToLatest = !this.pinnedToLatest;
143 },
Pablo Gamito7099c952020-06-10 17:31:28 +0100144 scrollToRow(index) {
145 if (!this.$refs.loglist) {
Pablo Gamito677dbec2020-06-05 16:51:19 +0100146 return;
147 }
148
Pablo Gamito7099c952020-06-10 17:31:28 +0100149 const itemOffset = this.$refs.loglist.virtual.getOffset(index);
150 const itemSize = 35;
151 const loglistSize = this.$refs.loglist.getClientSize();
Pablo Gamitof2f36d32020-05-29 14:19:23 +0100152
Pablo Gamito7099c952020-06-10 17:31:28 +0100153 this.$refs.loglist.scrollToOffset(itemOffset - loglistSize + itemSize);
Pablo Gamito396ab562020-06-01 10:25:12 +0100154 },
Pablo Gamitof0031c42020-06-26 15:35:42 +0100155 getLastOccuredIndex(data, timestamp) {
156 if (this.data.length === 0) {
157 return 0;
158 }
159 return findLastMatchingSorted(data,
160 (array, idx) => array[idx].timestamp <= timestamp);
161 },
Adam Pardylf1af74f2019-09-16 12:18:15 +0200162 },
163 watch: {
Pablo Gamito677dbec2020-06-05 16:51:19 +0100164 pinnedToLatest(isPinned) {
165 if (isPinned) {
Pablo Gamito7099c952020-06-10 17:31:28 +0100166 this.scrollToRow(this.lastOccuredVisibleIndex);
Pablo Gamito677dbec2020-06-05 16:51:19 +0100167 }
168 },
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100169 currentTimestamp: {
Adam Pardylf1af74f2019-09-16 12:18:15 +0200170 immediate: true,
Pablo Gamito7099c952020-06-10 17:31:28 +0100171 handler(newTimestamp) {
Pablo Gamito6c71e1f2020-06-05 19:01:08 +0100172 this.prevLastOccuredIndex = this.lastOccuredIndex;
Pablo Gamitof0031c42020-06-26 15:35:42 +0100173 this.lastOccuredIndex = this.getLastOccuredIndex(this.data, newTimestamp);
Pablo Gamito677dbec2020-06-05 16:51:19 +0100174
175 if (this.pinnedToLatest) {
Pablo Gamito7099c952020-06-10 17:31:28 +0100176 this.scrollToRow(this.lastOccuredVisibleIndex);
Pablo Gamito677dbec2020-06-05 16:51:19 +0100177 }
Adam Pardylf1af74f2019-09-16 12:18:15 +0200178 },
179 }
180 },
181 props: ['file'],
182 computed: {
Pablo Gamito7099c952020-06-10 17:31:28 +0100183 lastOccuredVisibleIndex() {
Pablo Gamitof0031c42020-06-26 15:35:42 +0100184 return this.getLastOccuredIndex(this.processedData, this.currentTimestamp);
Pablo Gamito7099c952020-06-10 17:31:28 +0100185 },
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100186 currentTimestamp() {
187 return this.$store.state.currentTimestamp;
Adam Pardylf1af74f2019-09-16 12:18:15 +0200188 },
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100189 processedData() {
190 const filteredData = this.data.filter(line => {
Pablo Gamito42cef4b2020-06-26 16:55:33 +0100191 if (this.selectedLogLevels.length > 0 &&
192 !this.selectedLogLevels.includes(line.level.toLowerCase())) {
193 return false;
194 }
195
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100196 if (this.sourceFiles.includes(this.selectedSourceFile)) {
197 // Only filter once source file is fully inputed
198 if (line.at != this.selectedSourceFile) {
199 return false;
200 }
201 }
202
Pablo Gamito42cef4b2020-06-26 16:55:33 +0100203 if (this.selectedTags.length > 0 && !this.selectedTags.includes(line.tag)) {
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100204 return false;
205 }
206
207 if (this.searchInput && !line.text.includes(this.searchInput)) {
208 return false;
209 }
210
211 return true;
212 });
213
Pablo Gamito7099c952020-06-10 17:31:28 +0100214 for (const entry of filteredData) {
215 entry.new = this.prevLastOccuredIndex < entry.index &&
216 entry.index <= this.lastOccuredIndex;
217 entry.occured = entry.index <= this.lastOccuredIndex;
Pablo Gamito0c14b122020-07-24 11:38:19 +0100218 entry.justInactivated = this.lastOccuredIndex < entry.index &&
219 entry.index <= this.prevLastOccuredIndex;
Pablo Gamito7099c952020-06-10 17:31:28 +0100220
221 // Force refresh if any of these changes
Pablo Gamito0c14b122020-07-24 11:38:19 +0100222 entry.uid = `${entry.index}${entry.new ? '-new' : ''}${entry.index}${entry.justInactivated ? '-just-inactivated' : ''}${entry.occured ? '-occured' : ''}`
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100223 }
224
225 return filteredData;
226 }
Adam Pardylf1af74f2019-09-16 12:18:15 +0200227 },
Pablo Gamito7099c952020-06-10 17:31:28 +0100228 components: {
229 'virtual-list': VirtualList,
230 'logentry': LogEntryComponent,
231 }
Adam Pardylf1af74f2019-09-16 12:18:15 +0200232}
233
234</script>
235<style>
Pablo Gamito050e20b2020-07-01 16:41:56 +0100236.container {
237 display: flex;
238 flex-wrap: wrap;
239}
240
Pablo Gamito677dbec2020-06-05 16:51:19 +0100241.filters, .navigation {
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100242 width: 100%;
243 display: flex;
Pablo Gamito677dbec2020-06-05 16:51:19 +0100244 flex-direction: row;
245 align-items: center;
246}
247
248.navigation {
249 justify-content: flex-end;
250}
251
252.navigation > button {
253 margin: 0;
Pablo Gamito84bd8ad2020-06-01 13:46:41 +0100254}
255
256.filters > div {
257 margin: 10px;
258}
259
Pablo Gamito7099c952020-06-10 17:31:28 +0100260.log-header {
261 display: inline-flex;
262 color: var(--md-theme-default-text-accent-on-background, rgba(0,0,0,0.54));
263 font-weight: bold;
Adam Pardylf1af74f2019-09-16 12:18:15 +0200264}
265
Pablo Gamito7099c952020-06-10 17:31:28 +0100266.log-header > div {
267 padding: 6px 10px;
268 border-bottom: 1px solid #f1f1f1;
Adam Pardylf1af74f2019-09-16 12:18:15 +0200269}
270
Pablo Gamito7099c952020-06-10 17:31:28 +0100271.log-header .time-column {
272 width: 13em;
Adam Pardylf1af74f2019-09-16 12:18:15 +0200273}
274
Pablo Gamito7099c952020-06-10 17:31:28 +0100275.log-header .tag-column {
276 width: 10em;
Adam Pardyl494b54f2019-09-25 11:59:23 +0200277}
278
Pablo Gamito7099c952020-06-10 17:31:28 +0100279.log-header .at-column {
280 width: 30em;
Adam Pardyl494b54f2019-09-25 11:59:23 +0200281}
282
Pablo Gamito7099c952020-06-10 17:31:28 +0100283.column-title {
284 font-size: 12px;
Pablo Gamito6c71e1f2020-06-05 19:01:08 +0100285}
Pablo Gamitof0031c42020-06-26 15:35:42 +0100286
287.no-logs-message {
288 margin: 15px;
289 display: flex;
290 align-content: center;
291 align-items: center;
292}
293
294.no-logs-message .message {
295 margin-left: 10px;
296 font-size: 15px;
297}
Adam Pardylf1af74f2019-09-16 12:18:15 +0200298</style>