Skip to content

Commit 4822fe0

Browse files
committed
Adds vue tag utils (TlpPap, DateInfo).
CaseImporter pre-version UI.
1 parent 2cac523 commit 4822fe0

5 files changed

Lines changed: 1175 additions & 1159 deletions

File tree

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
<script>
2+
import TlpPap from "../utils/TlpPap.vue";
3+
import DateInfo from "../utils/DateInfo.vue";
4+
import {Button, Card, Column, DataTable, FileUpload, Panel, ProgressBar, Tag} from "primevue";
5+
import {BlobReader, TextWriter, ZipReader} from "@zip.js/zip.js";
6+
import Color from 'color';
7+
8+
export default {
9+
props: {
10+
dataCsrfToken: String,
11+
},
12+
data() {
13+
return {
14+
currentCaseFile: null,
15+
state: {
16+
title: 'Please choose a file',
17+
error: null,
18+
progress: 0,
19+
determinate: true,
20+
},
21+
caseInfo: null,
22+
manifestInfo: null,
23+
caseEntries: [],
24+
_styles: {},
25+
};
26+
},
27+
components: {
28+
Button,
29+
Card,
30+
Column,
31+
DateInfo,
32+
DataTable,
33+
FileUpload,
34+
Panel,
35+
ProgressBar,
36+
Tag,
37+
TlpPap,
38+
},
39+
created() {
40+
this.$logger(this, 'CaseImporter');
41+
this.$cache.retrieve('all_styles').then((styles) => {
42+
this.$debug('got styles', styles);
43+
this._styles = styles;
44+
});
45+
},
46+
methods: {
47+
_setState(progress, title, error) {
48+
if (title !== undefined) {
49+
this.state.title = title;
50+
}
51+
52+
this.state.error = error || null;
53+
54+
if (progress !== undefined) {
55+
if (progress < 0) {
56+
this.state.determinate = false;
57+
}
58+
else {
59+
this.state.determinate = true;
60+
this.state.progress = Math.floor(progress * 100);
61+
}
62+
}
63+
},
64+
fileSelected(evt) {
65+
this._setState(-1, 'Parsing metadata ...');
66+
67+
for(let file of evt.files) {
68+
this.parseFile(file);
69+
return; // parse only one file
70+
}
71+
},
72+
async parseFile(file) {
73+
this.currentCaseFile = file;
74+
const zipFileReader = new BlobReader(file);
75+
const zipReader = new ZipReader(zipFileReader);
76+
const zipEntries = await zipReader.getEntries();
77+
const zipEntriesCount = zipEntries.length;
78+
79+
let entryProcessed = 0;
80+
for(let zipEntry of zipEntries) {
81+
if (zipEntry.filename.endsWith('data.json')) {
82+
this.$debug(`Processing: ${zipEntry.filename}`);
83+
const jsonWriter = new TextWriter();
84+
const jsonContent = await zipEntry.getData(jsonWriter);
85+
const data = JSON.parse(jsonContent);
86+
let typeStr = 'Case';
87+
const filenameParts = zipEntry.filename.split('/');
88+
if (filenameParts.length > 1) {
89+
typeStr = filenameParts[0];
90+
}
91+
let newEntry = {
92+
filename: zipEntry.filename,
93+
name: data.name,
94+
super_type: typeStr,
95+
type: data.type?.name,
96+
thumbnail: Boolean(data.thumbnail),
97+
file: data.size_in_bytes > 0,
98+
warning: false,
99+
data: data,
100+
};
101+
if (zipEntry.filename === 'data.json') {
102+
this.caseInfo = newEntry;
103+
this.$debug('caseInfo', newEntry);
104+
}
105+
else {
106+
this.caseEntries.push(newEntry);
107+
}
108+
}
109+
else if (zipEntry.filename === 'manifest.json') {
110+
this.$debug(`Processing: ${zipEntry.filename}`);
111+
const jsonWriter = new TextWriter();
112+
const jsonContent = await zipEntry.getData(jsonWriter);
113+
const data = JSON.parse(jsonContent);
114+
this.manifestInfo = data;
115+
}
116+
entryProcessed += 1;
117+
this._setState(entryProcessed/zipEntriesCount);
118+
}
119+
await zipReader.close();
120+
if (this.caseInfo === null) {
121+
this._setState(1, "Metadata not found.", "No case information found.");
122+
}
123+
else if (this.manifestInfo === null) {
124+
this._setState(1, "Metadata not found.", "No manifest information found.");
125+
}
126+
else {
127+
this._setState(1, "Metadata parsed. Please review then hit 'Import case' button.");
128+
}
129+
},
130+
reset(cb) {
131+
this.currentCaseFile = null;
132+
this.state.determinate = true;
133+
this.state.progress = 0;
134+
this.state.title = 'Please choose a file';
135+
this.state.error = null;
136+
this.caseInfo = null;
137+
this.manifestInfo = null;
138+
this.caseEntries = [];
139+
cb?.();
140+
},
141+
importCurrentCase() {
142+
143+
},
144+
iconClass(data) {
145+
if (data.super_type === 'Case') {
146+
return 'fa fa-folder';
147+
}
148+
if (data.super_type === 'SubGraph') {
149+
return 'nf nf-md-hubspot';
150+
}
151+
if (data.super_type in this._styles) {
152+
let iconCls = this._styles[data.super_type]?.['icon-font-classname'];
153+
let iconSet = iconCls.split('-');
154+
return `${iconSet[0]} ${iconCls}`;
155+
}
156+
return '';
157+
},
158+
typeColor(data) {
159+
if (data.super_type === 'Case') {
160+
return '#A991D4';
161+
}
162+
if (data.super_type === 'SubGraph') {
163+
return 'rgba(42, 123, 155, 1)';
164+
}
165+
if (data.super_type in this._styles) {
166+
return this._styles[data.super_type]?.color;
167+
}
168+
return null;
169+
},
170+
rs_obj(data) {
171+
let colorStr = this.typeColor(data);
172+
if (!colorStr) return {};
173+
let baseStyle = {
174+
'--super-type-bg-color': this.typeColor(data) || 'red',
175+
//'top': '39px',
176+
};
177+
let colorObj = Color(colorStr);
178+
if (colorObj.luminosity() < 0.6) {
179+
//baseStyle['--super-type-fg-color'] = 'white';
180+
}
181+
return baseStyle;
182+
},
183+
},
184+
computed: {
185+
stateMode() {
186+
return this.state.determinate ? 'determinate' : 'indeterminate';
187+
},
188+
invalidImport() {
189+
return this.caseInfo === null || this.manifestInfo === null || this.state.error !== null;
190+
},
191+
},
192+
};
193+
</script>
194+
<template>
195+
<div class="case-importer">
196+
<FileUpload ref="fileChooser"
197+
accept="application/zip"
198+
@select="fileSelected">
199+
<template #header="{chooseCallback, clearCallback, files}">
200+
<div v-if="files.length">
201+
<Button label="Cancel"
202+
icon="pi pi-undo" severity="danger"
203+
@click="reset(clearCallback)"/>
204+
<Button label="Import case"
205+
icon="pi pi-file-arrow-up" severity="primary"
206+
class="ms-2"
207+
:disabled="invalidImport"
208+
@click="importCurrentCase()"/>
209+
</div>
210+
<div v-else>
211+
<Button label="Choose"
212+
icon="fa fa-folder-open" severity="primary"
213+
@click="chooseCallback"/>
214+
</div>
215+
</template>
216+
<template #content>
217+
<div class="">
218+
<span>{{ state.title }}</span>
219+
<span v-if="state.error" class="text-danger ms-2">
220+
<i class="pi pi-exclamation-triangle"></i> {{ state.error }}
221+
</span>
222+
</div>
223+
<ProgressBar :value="state.progress" :mode="stateMode"></ProgressBar>
224+
</template>
225+
</FileUpload>
226+
<div class="row mt-2">
227+
<div class="col-12">
228+
<Panel v-show="!invalidImport">
229+
<template #header>
230+
<h4>{{ caseInfo?.name }}</h4>
231+
</template>
232+
<div class="row">
233+
<div class="col-lg-6">
234+
<table class="table table-sm">
235+
<tbody>
236+
<tr>
237+
<td>Created at</td>
238+
<td>
239+
<DateInfo v-if="caseInfo?.data.created_at" :date="caseInfo?.data.created_at"/>
240+
</td>
241+
</tr>
242+
<tr>
243+
<td>Updated at</td>
244+
<td>
245+
<DateInfo v-if="caseInfo?.data.updated_at" :date="caseInfo?.data.updated_at"/>
246+
</td>
247+
</tr>
248+
<tr>
249+
<td>TLP/PAP</td>
250+
<td>
251+
<TlpPap :tlp="caseInfo?.data.tlp" :pap="caseInfo?.data.pap" />
252+
</td>
253+
</tr>
254+
</tbody>
255+
</table>
256+
</div>
257+
<div class="col-lg-6">
258+
<h5>Description</h5>
259+
<pre>{{caseInfo?.data.description}}</pre>
260+
</div>
261+
</div>
262+
<h4>Case content</h4>
263+
<DataTable :value="caseEntries"
264+
rowGroupMode="subheader" groupRowsBy="super_type"
265+
:rowStyle="rs_obj"
266+
:pt="{
267+
rowGroupHeader: (options) => ({
268+
style: rs_obj(options.props.rowData),
269+
}),
270+
}">
271+
<Column field="name" header="Name">
272+
<template #body="slotProps">
273+
<div class="type-colored">
274+
<i :class="iconClass(slotProps.data)"></i>
275+
<span class="ms-1">{{slotProps.data.name}}</span>
276+
</div>
277+
</template>
278+
</Column>
279+
<Column field="super_type" header="Super Type"/>
280+
<Column field="type" header="Type"/>
281+
<Column field="thumbnail" header="Has thumbnail">
282+
<template #body="slotProps">
283+
<Tag v-if="slotProps.data.thumbnail" icon="pi pi-check" severity="secondary"></Tag>
284+
</template>
285+
</Column>
286+
<Column field="file" header="Has file">
287+
<template #body="slotProps">
288+
<Tag v-if="slotProps.data.file" icon="pi pi-check" severity="secondary"></Tag>
289+
</template>
290+
</Column>
291+
<Column field="warning" header="Status">
292+
<template #body="slotProps">
293+
<Tag severity="success">Ready</Tag>
294+
</template>
295+
</Column>
296+
<template #groupheader="slotProps">
297+
<div class="gap-2 type-colored white-outline">
298+
<i :class="iconClass(slotProps.data)"></i>
299+
<span class="ms-1 fw-bold">{{ slotProps.data.super_type }}</span>
300+
</div>
301+
</template>
302+
</DataTable>
303+
</Panel>
304+
</div>
305+
</div>
306+
</div>
307+
</template>
308+
<style scoped>
309+
.p-progressbar {
310+
--p-fileupload-progressbar-height: 1.25em;
311+
}
312+
:global(tr:has(.type-colored)) {
313+
--falloff-distance: 5px;
314+
--super-type-bg-color: rgba(42, 123, 155, 1);
315+
--super-type-fg-color: var(--p-datatable-row-color);
316+
background: linear-gradient(
317+
90deg,
318+
var(--super-type-bg-color) 0%,
319+
var(--super-type-bg-color) 4px,
320+
white var(--falloff-distance)
321+
) !important;
322+
}
323+
:global(tr.p-datatable-row-group-header:has(.type-colored)) {
324+
color: var(--super-type-fg-color) !important;
325+
--falloff-distance: 50%;
326+
}
327+
:global(.white-outline) {
328+
--offset: 2px;
329+
--distance: 1px;
330+
text-shadow:
331+
white 0 var(--offset) var(--distance),
332+
white 0 calc(-1*var(--offset)) var(--distance),
333+
white var(--offset) 0 var(--distance),
334+
white calc(-1*var(--offset)) 0 var(--distance),
335+
white var(--offset) var(--offset) var(--distance),
336+
white calc(-1*var(--offset)) var(--offset) var(--distance),
337+
white calc(-1*var(--offset)) calc(-1*var(--offset)) var(--distance),
338+
white var(--offset) calc(-1*var(--offset)) var(--distance);
339+
}
340+
</style>

0 commit comments

Comments
 (0)