-
Notifications
You must be signed in to change notification settings - Fork 19
/
index.html
343 lines (293 loc) · 10.7 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
<!DOCTYPE html>
<html>
<head>
<title>TierZeroTable</title>
<style>
body {
font-family: 'Nunito Sans', Arial, sans-serif;
background-color: #fff;
color: #000;
padding: 20px;
}
.table-container {
overflow-x: auto;
max-width: 100%;
}
.search-container {
max-width: 100%;
overflow-x: auto;
}
.search-input {
margin-bottom: 10px;
width: 100%;
min-width: 1000px; /* Match the table's min-width */
padding: 8px;
font-size: 16px;
box-sizing: border-box;
}
table {
border-collapse: collapse;
width: 100%;
min-width: 1000px;
margin-bottom: 20px;
table-layout: fixed;
}
th, td {
text-align: left;
padding: 7px;
border-bottom: 1px solid #888;
vertical-align: top;
word-wrap: break-word;
word-break: break-word;
}
th {
background-color: #999999;
color: #000;
border-bottom: 2px solid #000;
cursor: pointer;
position: relative;
}
th:hover {
background-color: #bbb;
}
.sort-icon {
position: absolute;
right: 10px;
font-size: 12px;
}
.sorted-asc::after {
content: ' ▲';
}
.sorted-desc::after {
content: ' ▼';
}
tr:nth-child(even) {
background-color: #D5D5D5;
}
a:link, a:visited {
color: blue;
text-decoration: underline;
}
/* Truncate class to limit the number of lines */
.truncate {
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
}
.expand-btn {
color: blue;
cursor: pointer;
display: block;
margin-top: 5px;
background-color: transparent;
border: none;
padding: 0;
font-size: 14px;
}
/* Style for "No results found" message */
.no-results {
text-align: center;
font-size: 18px;
margin-top: 20px;
}
</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito+Sans">
</head>
<body>
<h1 class="title">TierZeroTable</h1>
<p class="line1">Table of AD and Azure assets and whether they belong to Tier Zero.</p>
<p class="line2">Description of table columns and additional resources can be found here: <a href="https://github.com/SpecterOps/TierZeroTable/">https://github.com/SpecterOps/TierZeroTable</a></p>
<p class="line3">Hint: Click on a header to sort the table alphabetically.</p>
<!-- Search Input inside a container -->
<div class="search-container">
<input type="text" id="searchInput" class="search-input" placeholder="Search...">
</div>
<div class="table-container">
<table id="csvTable">
<!-- Table content will be inserted here by JavaScript -->
</table>
</div>
<!-- "No results found" message -->
<div id="noResultsMessage" class="no-results" style="display: none;">No results found.</div>
<!-- Include Papa Parse Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
<!-- Include LinkifyJS Library -->
<script src="https://cdn.jsdelivr.net/npm/linkifyjs@3.0.3/dist/linkify.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/linkifyjs@3.0.3/dist/linkify-html.min.js"></script>
<script>
// URL of the CSV file
var csvFileUrl = 'https://raw.githubusercontent.com/SpecterOps/TierZeroTable/main/TierZeroTable.csv';
var table; // Declare table variable in global scope
var sortDirections = {}; // To keep track of sort directions for each column
var originalData = []; // Global variable to store original data
var headers; // Global variable to store headers
var currentData = []; // Current displayed data
var currentSortedColumnIndex = null; // Currently sorted column index
var currentSortDirection = 'none'; // Current sort direction
// Function to create and populate the table
function createTable(data, sortedColumnIndex = null, sortDirection = 'none') {
table = document.getElementById('csvTable');
table.innerHTML = ''; // Clear existing table content
if (data.length === 0) {
document.getElementById('noResultsMessage').style.display = 'block';
return;
} else {
document.getElementById('noResultsMessage').style.display = 'none';
}
headers = Object.keys(data[0]);
var thead = document.createElement('thead');
var headerRow = document.createElement('tr');
headers.forEach(function(header, index) {
var th = document.createElement('th');
th.textContent = header;
th.onclick = function() {
sortTable(index);
};
th.title = 'Click to sort by ' + header;
th.style.position = 'relative';
// Add sort icon span
var sortIcon = document.createElement('span');
sortIcon.className = 'sort-icon';
th.appendChild(sortIcon);
// Add sort indicator if this is the sorted column
if (index === sortedColumnIndex) {
if (sortDirection === 'asc') {
th.classList.add('sorted-asc');
} else if (sortDirection === 'desc') {
th.classList.add('sorted-desc');
}
}
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Populate table with data
var tbody = document.createElement('tbody');
data.forEach(function(rowData) {
if (rowData[headers[0]] !== "") {
var row = document.createElement('tr');
headers.forEach(function(header) {
var cell = document.createElement('td');
var cellText = rowData[header];
// Process the cell text to convert URLs into hyperlinks using LinkifyJS
var processedText = linkifyHtml(cellText, {
defaultProtocol: 'https'
});
// Create a temporary element to strip HTML tags and get text length
var tempElement = document.createElement('div');
tempElement.innerHTML = processedText;
var textContent = tempElement.textContent || tempElement.innerText || '';
// Adjusted length threshold to 100 characters
if (textContent.length > 100) { // Adjust the length as needed
var span = document.createElement('span');
span.className = 'truncate';
span.innerHTML = processedText; // Full processed text with hyperlinks
var expandBtn = document.createElement('button');
expandBtn.className = 'expand-btn';
expandBtn.textContent = 'Read more';
expandBtn.setAttribute('aria-expanded', 'false');
expandBtn.onclick = function() {
if (span.classList.contains('truncate')) {
// Expand the text
span.classList.remove('truncate');
expandBtn.textContent = 'Read less';
expandBtn.setAttribute('aria-expanded', 'true');
} else {
// Collapse the text
span.classList.add('truncate');
expandBtn.textContent = 'Read more';
expandBtn.setAttribute('aria-expanded', 'false');
}
};
cell.appendChild(span);
cell.appendChild(expandBtn);
} else {
cell.innerHTML = processedText;
}
row.appendChild(cell);
});
tbody.appendChild(row);
}
});
table.appendChild(tbody);
}
// Load Papa Parse library and initialize the table
window.onload = function() {
Papa.parse(csvFileUrl, {
download: true,
header: true,
complete: function(results) {
originalData = results.data.filter(function(row) {
return Object.values(row).some(function(value) {
return value !== null && value !== '';
});
});
currentData = originalData.slice(); // Copy of the original data
createTable(currentData);
// Add search functionality
document.getElementById('searchInput').addEventListener('keyup', function() {
var filter = this.value.toLowerCase();
var filteredData = originalData.filter(function(row) {
return Object.values(row).some(function(value) {
return value.toLowerCase().indexOf(filter) > -1;
});
});
// Apply current sorting to the filtered data
if (currentSortedColumnIndex !== null && currentSortDirection !== 'none') {
var n = currentSortedColumnIndex;
var dir = currentSortDirection;
filteredData.sort(function(a, b) {
var x = a[headers[n]] ? a[headers[n]].toLowerCase() : '';
var y = b[headers[n]] ? b[headers[n]].toLowerCase() : '';
if (x < y) return dir === 'asc' ? -1 : 1;
if (x > y) return dir === 'asc' ? 1 : -1;
return 0;
});
}
currentData = filteredData; // Update current data
createTable(filteredData, currentSortedColumnIndex, currentSortDirection);
});
}
});
};
// Sort by header function with sort indicators
function sortTable(n) {
var currentDir = sortDirections[n] || 'none';
// Cycle through sort directions: none -> asc -> desc -> none
if (currentDir === 'none') {
sortDirections[n] = 'asc';
} else if (currentDir === 'asc') {
sortDirections[n] = 'desc';
} else {
sortDirections[n] = 'none';
}
var dir = sortDirections[n];
if (dir === 'asc' || dir === 'desc') {
// Update current sorted column and direction
currentSortedColumnIndex = n;
currentSortDirection = dir;
} else {
// Reset sorting
currentSortedColumnIndex = null;
currentSortDirection = 'none';
sortDirections[n] = 'none';
}
var dataToSort = currentData.slice(); // Make a copy of the current data
if (dir === 'asc' || dir === 'desc') {
dataToSort.sort(function(a, b) {
var x = a[headers[n]] ? a[headers[n]].toLowerCase() : '';
var y = b[headers[n]] ? b[headers[n]].toLowerCase() : '';
if (x < y) return dir === 'asc' ? -1 : 1;
if (x > y) return dir === 'asc' ? 1 : -1;
return 0;
});
} else {
dataToSort = originalData.slice(); // Return to original order
}
currentData = dataToSort; // Update current data
createTable(dataToSort, currentSortedColumnIndex, currentSortDirection);
}
</script>
</body>
</html>