Skip to content
Snippets Groups Projects
Commit 08b88cec authored by Alex Complojer's avatar Alex Complojer
Browse files

column filter for tag columns & json load from uri

parent 501bfe50
No related branches found
No related tags found
No related merge requests found
......@@ -23,3 +23,17 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.phpunit.result.cache
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
\ No newline at end of file
......@@ -10,6 +10,9 @@ class JsonProxy extends Controller
public function get(Request $request)
{
$uri = $request->query('uri');
$token = $request->query('private_token');
if($request->query('private_token')) $uri .= "&private_token=".$token;
try
{
......
......@@ -13,7 +13,9 @@
>
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>Package Explorer</v-toolbar-title>
<v-toolbar-title>
Package Explorer ({{ entries.length }})
</v-toolbar-title>
</v-toolbar>
</template>
......@@ -119,9 +121,58 @@
</template>
<template v-for="h in headers" v-slot:[`header.${h.value}`]="{ header }">
<v-tooltip top>
<div style="float: left;" v-if="header.filterVals" :key="h.value">
<v-menu
:close-on-content-click="false"
:nudge-width="200"
offset-y
transition="slide-y-transition"
left
fixed
>
<template v-slot:activator="{ on, attrs }">
<span
:class="{
act: Object.values(header.activeVals).reduce((a, o) => {
return a || o;
}, false)
}"
v-on="on"
v-bind="attrs"
style="line-height:16px"
><v-icon small class="mr-1">mdi-filter</v-icon></span
>
</template>
<v-list flat dense class="pa-0" max-height="300" color="white">
<template v-for="item in header.filterVals">
<v-list-item
:key="`${item}`"
:value="item"
:ripple="false"
dense
style="background:white;"
>
<v-list-item-action style="margin:0;" class="mr-2">
<v-checkbox
v-model="header.activeVals[item]"
@click="toggle(header, item)"
color="primary"
dense
></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="item"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
<v-divider></v-divider>
<v-row no-gutters> </v-row>
</v-list>
</v-menu>
</div>
<v-tooltip top :key="h.text">
<template v-slot:activator="{ on }">
<span v-on="on">{{ h.text }}</span>
<span v-on="on" style="line-height:16px">{{ h.text }}</span>
</template>
<span>{{ h.tooltip }}</span>
</v-tooltip>
......@@ -131,19 +182,27 @@
v-for="head in headers"
v-slot:[`item.${head.value}`]="{ item }"
>
<div v-if="head.type == 'implode'">
<div v-for="text in resolve(head.value, item)" style="font-size:11px">
<div v-if="head.type == 'implode'" :key="head.value">
<div
v-for="text in resolve(head.value, item)"
style="font-size:11px"
:key="text"
>
{{ text }}
</div>
</div>
<div v-if="head.type == 'implode_tag'">
<div v-for="text in resolve(head.value, item)" style="font-size:11px">
<div v-if="head.type == 'implode_tag'" :key="head.value">
<div
v-for="text in resolve(head.value, item)"
style="font-size:11px"
:key="text"
>
<v-chip small class="mb-1">{{ text }}</v-chip>
</div>
</div>
<div v-if="head.type == 'progress'">
<div v-if="head.type == 'progress'" :key="head.value">
<v-progress-linear
:value="item.progress"
:color="palette[0]"
......@@ -158,7 +217,7 @@
</div>
</div>
<div v-if="head.type == 'workload'">
<div v-if="head.type == 'workload'" :key="head.value">
<div>
<v-icon size="15" v-if="normalize(item.workload_total) < 0.2"
>mdi-weight</v-icon
......@@ -197,7 +256,7 @@
</div>
</div>
<div v-if="head.type == 'chart'">
<div v-if="head.type == 'chart'" :key="head.value">
<div
class="ma-1"
v-if="
......@@ -205,7 +264,10 @@
'statistics.licenses.license_audit_findings.all_licenses'
"
>
<div class="ma-1 text-center" style="font-weight:bold;min-height:21px">
<div
class="ma-1 text-center"
style="font-weight:bold;min-height:21px"
>
{{ sums["all_" + item.uid] }}
</div>
......@@ -237,7 +299,10 @@
class="ma-1"
v-if="head.value == 'statistics.licenses.license_scanner_findings'"
>
<div class="ma-1 text-center" style="font-weight:bold;min-height:21px">
<div
class="ma-1 text-center"
style="font-weight:bold;min-height:21px"
>
{{ sums["scan_" + item.uid] }}
</div>
......@@ -266,7 +331,10 @@
</div>
</div>
<div v-if="head.type == 'match' && item.debian_matching">
<div
v-if="head.type == 'match' && item.debian_matching"
:key="head.value"
>
<v-tooltip top>
<template v-slot:activator="{ on }">
<v-icon
......@@ -310,6 +378,7 @@
<div
v-if="head.type == 'string'"
style="font-size:11px;word-break: break-all;"
:key="head.value"
>
{{ item[head.value] }}
</div>
......@@ -376,6 +445,14 @@ export default {
};
},
methods: {
toggle(header, item) {
this.$emit("filter-clicked", {
col: header.value,
val: item,
active: header.activeVals,
item: item
});
},
getCDef(e) {
Vue.nextTick(() => {
this.getCharts(e);
......@@ -448,6 +525,9 @@ export default {
return "red";
},
columnFiltered: function(items) {
return items.reduce((acc, cur) => acc || cur, false);
},
sortNamesAndValueArrays: function(names = [], values = []) {
var list = [];
for (var j = 0; j < names.length; j++)
......
......@@ -89,8 +89,17 @@ export default {
if (this.$route.query.json) {
this.loading = true;
let uri = this.$route.query.json;
// if private_token is set, it should be a gitlab uri
if(this.$route.query.private_token) {
let parts = this.$route.query.json.split("/repository/files/");
let subparts = parts[parts.length - 1].split("/raw?ref");
uri = parts[0] + "/repository/files/" + encodeURIComponent(encodeURIComponent(subparts[0])) + "/raw?ref" + subparts[1] + "&private_token=" + this.$route.query.private_token
}
axios
.get("/json?uri=" + this.$route.query.json)
.get("/json?uri=" + uri, { private_token : this.$route.query.private_token })
.then(response => {
if(response.data.data.tool && response.data.data.tool.name == "aliens4friends.harvest") {
this.$store.dispatch("file/saveFile", { json: response.data.data });
......
......@@ -272,8 +272,49 @@
</v-container>
<v-container fill-height fluid>
<v-row>
<v-row no-gutters class="text-right">
<v-col cols="12">
<div style="display:inline-block" class="mr-4">
<v-switch
v-model="filterMode"
label="Exclude selection"
color="red"
class="ma-0"
value="exclusive"
hide-details
@click="filterChange = true"
></v-switch>
</div>
<v-btn
:disabled="!filterChange"
@click="filter(needle)"
>
Apply
<v-icon
right
dark
>
mdi-filter
</v-icon>
</v-btn>
<v-btn
:disabled="needle == '' && Object.keys(columnFilter).length == 0"
outlined
@click="clearFilter"
>
Clear
<v-icon
right
dark
>
mdi-close
</v-icon>
</v-btn>
</v-col>
</v-row>
<v-row class="mt-0">
<v-col :class="{ filtered : isFiltered }">
<table-component
:entries="current"
......@@ -281,6 +322,7 @@
:stats="total_stats"
:palette="palette"
:colors="colors"
@filter-clicked="triggerSearch"
ident="Meta"
></table-component>
</v-col>
......@@ -314,6 +356,10 @@ export default {
data() {
return {
needle: "",
filterChange: false,
filtered: false,
filterMode: "inclusive",
columnFilter: {},
headers: headers.headers,
snackbar: false,
current: [],
......@@ -327,17 +373,8 @@ export default {
min: 0,
max: 0
},
palette: colors.colors,
colors: {
"Known provenance": "#4ea262",
"Unknown provenance": "#808080",
"Audit done": "#4ea262",
"Audit todo": "#ff3232",
audit_total: "#777777",
upstream_source_total: "#DDDDDD",
total: "#999999",
"Audit not required": "#999999"
}
palette: colors.palette,
colors: colors.colors
};
},
computed: {
......@@ -364,14 +401,16 @@ export default {
if (source[i].statistics) {
const filestats = source[i].statistics.files;
// es fehlen oft die audit-knoten
if (isNaN(filestats.unknown_provenance)) filestats.unknown_provenance = 0;
// TODO: make model
if (isNaN(filestats.unknown_provenance))
filestats.unknown_provenance = 0;
if (isNaN(filestats.known_provenance)) filestats.known_provenance = 0;
if (isNaN(filestats.total)) filestats.total = 0;
if (isNaN(filestats.audit_total)) filestats.audit_total = 0;
if (isNaN(filestats.audit_done)) filestats.audit_done = 0;
if (isNaN(filestats.audit_to_do)) filestats.audit_to_do = 0;
if (isNaN(filestats.upstream_source_total)) filestats.upstream_source_total = 0;
if (isNaN(filestats.upstream_source_total))
filestats.upstream_source_total = 0;
// predefine license colors
let licenses = [];
......@@ -423,6 +462,37 @@ export default {
this.progress = parseInt((all.audited / all.audit_total) * 100);
this.total_stats = all;
// generate column filter values
for (var i = 0; i < this.headers.length; i++) {
if (this.headers[i].autofilter) {
this.headers[i].filterVals = [];
this.headers[i].activeVals = {};
for (var a = 0; a < res.length; a++) {
let vals = this.resolve(this.headers[i].value, res[a]);
if (vals) {
if (this.headers[i].type == "chart") {
vals = vals.map(o => {
return o.shortname;
});
}
this.headers[i].filterVals = [
...new Set([...this.headers[i].filterVals, ...vals])
];
}
}
this.headers[i].filterVals.sort();
for (var a = 0; a < this.headers[i].filterVals.length; a++) {
this.headers[i].activeVals[this.headers[i].filterVals[a]] = false;
}
}
}
this.current = res;
return res;
......@@ -437,7 +507,7 @@ export default {
return res;
},
isFiltered: function() {
return this.needle != "";
return this.filtered;
},
stats: function() {
let res = {
......@@ -569,12 +639,17 @@ export default {
audit_all: {
title: "License types audited",
subtitle: "Results by human auditor analysis",
value: this.accumulatedLicenses("license_audit_findings.all_licenses")
value: this.accumulatedLicenses(
"license_audit_findings.all_licenses"
)
},
main_licenses: {
title: "Main license types",
subtitle: "Accumulated main licenses",
value: this.accumulatedLicenses("license_audit_findings.main_licenses", true)
value: this.accumulatedLicenses(
"license_audit_findings.main_licenses",
true
)
}
}
};
......@@ -588,8 +663,46 @@ export default {
return properties.reduce((prev, curr) => prev && prev[curr], obj);
},
triggerSearch(e) {
let needle = e;
// if column filter, register filter but do not trigger automagically
if (needle.col) {
this.filterChange = true;
this.columnFilter[needle.col] = {
state: needle.active,
needle: "",
key: needle.col
};
Object.keys(this.columnFilter).forEach((key, index) => {
Object.keys(this.columnFilter[key].state).forEach(
(innerKey, innerIndex) => {
if (this.columnFilter[key].state[innerKey]) {
this.columnFilter[key].needle += innerKey + " ";
}
}
);
});
return false;
}
this.filter(e);
},
clearFilter() {
this.columnFilter = [];
for (var i = 0; i < this.headers.length; i++) {
if (this.headers[i].activeVals) {
Object.keys(this.headers[i].activeVals).forEach((key, index) => {
this.headers[i].activeVals[key] = false;
});
}
}
this.filter("");
},
filter(needle) {
this.needle = needle;
......@@ -599,22 +712,47 @@ export default {
for (var a = 0; a < this.entries.length; a++) {
let found = true;
let str = JSON.stringify(this.entries[a]).toLowerCase() || "";
let cols_found = true;
// table column filter
Object.keys(this.columnFilter).forEach((key, index) => {
let col = this.resolve(key, this.entries[a]);
for (var i = 0; i < splitted.length; i++) {
found = str.indexOf(splitted[i].toLowerCase()) != -1 && found;
if (!!col) {
let colString = JSON.stringify(col).toLowerCase() || "";
let colNeedle = this.columnFilter[key].needle.split(" ");
for (var i = 0; i < colNeedle.length - 1; i++) {
let here =
colString.indexOf('"' + colNeedle[i].toLowerCase() + '"') != -1;
if (this.filterMode == "exclusive") here = !here;
cols_found = here && cols_found;
}
} else {
cols_found = false;
}
});
// global text search
if (this.needle != "") {
let str = JSON.stringify(this.entries[a]).toLowerCase() || "";
for (var i = 0; i < splitted.length; i++) {
found = str.indexOf(splitted[i].toLowerCase()) != -1 && found;
}
}
if (found) {
if (found && cols_found) {
res.push(this.entries[a]);
}
}
this.current = res;
this.filterChange = false;
this.filtered = this.current.length != this.entries.length;
},
accumulatedMainLicenses: function() {
let res = {};
for (let i = 0; i < this.current.length; i++) {
let license_names = [];
......@@ -712,13 +850,13 @@ export default {
data = sorted.values;
}
// grouping
if (grouping) {
const odata = data;
labels = labels.slice(0, grouping);
data = odata.slice(0, grouping);
// grouping
let others = odata.slice(grouping);
if (others.reduce((a, b) => a + b, 0) > 0) {
......@@ -827,4 +965,8 @@ export default {
color: green !important;
}
}
.act .theme--light.v-icon {
color: red;
}
</style>
exports.colors = [
exports.colors = {
"Known provenance": "#4ea262",
"Unknown provenance": "#808080",
"Audit done": "#4ea262",
"Audit todo": "#ff3232",
"audit_total": "#777777",
"upstream_source_total": "#DDDDDD",
"total": "#999999",
"Audit not required": "#999999"
}
exports.palette = [
"#63b598",
"#ce7d78",
"#ea9e70",
......
......@@ -8,7 +8,6 @@ exports.headers = [
width: "220px",
tooltip: "Package name"
},
{
text: "Audit Progress",
align: "start",
......@@ -30,6 +29,8 @@ exports.headers = [
value: "statistics.licenses.license_audit_findings.main_licenses",
type: "implode_tag",
fixed: true,
autofilter: true,
sortable: false,
tooltip: "Main licenses"
},
{
......@@ -37,22 +38,27 @@ exports.headers = [
value: "tags.distro",
type: "implode",
fixed: true,
autofilter: true,
sortable: false,
tooltip: "Distro"
},
{
text: "Image",
value: "tags.image",
type: "implode",
fixed: true,
autofilter: true,
sortable: false,
tooltip: "Image"
},
{
text: "Machine",
value: "tags.machine",
type: "implode",
fixed: true,
filtered: true,
autofilter: true,
sortable: false,
tooltip: "Machine"
},
{
......@@ -60,6 +66,8 @@ exports.headers = [
value: "tags.release",
type: "implode",
fixed: true,
autofilter: true,
sortable: false,
tooltip: "Release"
},
{
......@@ -69,6 +77,8 @@ exports.headers = [
type: "chart",
width: "200px",
fixed: true,
autofilter: true,
sortable: false,
tooltip: "license_scanner_findings"
},
{
......@@ -78,6 +88,8 @@ exports.headers = [
type: "chart",
width: "200px",
fixed: true,
autofilter: true,
sortable: false,
tooltip: "all_licenses"
},
......@@ -87,6 +99,7 @@ exports.headers = [
align: "end",
type: "match",
width: "100px",
sortable: false,
fixed: true,
tooltip: "ip_matching_files / upstream_source_total"
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment