finished the first version of UI, refactoring and facelifts are pending

This commit is contained in:
Volodymyr Smirnov 2020-10-27 13:15:48 +02:00
parent e56f95e3a6
commit 61224d5cb5
11 changed files with 1554 additions and 7 deletions

View File

@ -1,6 +1,7 @@
using MalwareMultiScan.Api.Extensions;
using MalwareMultiScan.Api.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -18,6 +19,11 @@ namespace MalwareMultiScan.Api
public void ConfigureServices(IServiceCollection services)
{
services.AddDockerForwardedHeadersOptions();
services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = _configuration.GetValue<int>("MaxFileSize");
});
services.AddMongoDb(_configuration);
services.AddRabbitMq(_configuration);

View File

@ -15,6 +15,6 @@
"DatabaseName": "MalwareMultiScan",
"ResultsSubscriptionId": "mms.results",
"MaxFileSize": 1048576,
"MaxFileSize": 52428800,
"BackendsConfiguration": "backends.yaml"
}

View File

@ -25,9 +25,12 @@ namespace MalwareMultiScan.Backends.Backends.Implementations
return Scan();
}
private static Task<string[]> Scan()
private static async Task<string[]> Scan()
{
return Task.FromResult(new[] {"Malware.Dummy.Result"});
await Task.Delay(
TimeSpan.FromSeconds(5));
return new[] {"Malware.Dummy.Result"};
}
}
}

View File

@ -0,0 +1,79 @@
<template>
<div class="p-2 w-25">
<div :class="cardClass" class="card">
<h6 class="card-header">{{ id }}</h6>
<div class="card-body">
<b-skeleton-wrapper :loading="!result.completed">
<template #loading>
<b-skeleton/>
</template>
<div v-if="result.succeeded && !result.threats">No threats have been detected...</div>
<div v-if="result.succeeded === false">Scanning failed to complete...</div>
<ul v-if="result.threats" class="list-inline m-0">
<li v-for="threat in result.threats" v-bind:key="threat">{{ threat }}</li>
</ul>
</b-skeleton-wrapper>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue, {PropOptions} from 'vue'
import ScanResultEntry from '~/models/scan-result-entry';
export default Vue.extend({
name: 'scan-result-component',
props: {
id: {
type: String,
required: true
} as PropOptions<string>,
result: {
type: Object,
required: true
} as PropOptions<ScanResultEntry>
},
computed: {
cardClass() {
const result = this.result as ScanResultEntry;
return {
'succeeded': result.succeeded && !result.threats,
'detected': result.succeeded && result.threats,
'failed': result.succeeded === false,
};
}
}
});
</script>
<style lang="scss" scoped>
@import 'node_modules/bootstrap/scss/bootstrap.scss';
.card-header {
text-transform: uppercase;
}
.succeeded {
@extend .bg-success;
@extend .text-white;
}
.detected {
@extend .bg-danger;
@extend .text-white;
}
.failed {
@extend .bg-warning;
@extend .text-white;
}
</style>

View File

@ -0,0 +1,5 @@
export default interface ScanResultEntry {
readonly completed: boolean,
readonly succeeded: boolean | null,
readonly threats: string[]
}

View File

@ -0,0 +1,6 @@
import ScanResultEntry from "~/models/scan-result-entry";
export default interface ScanResult {
readonly id: string,
readonly results: { [id: string]: ScanResultEntry; }
}

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,8 @@
},
"devDependencies": {
"@nuxt/types": "^2.14.6",
"@nuxt/typescript-build": "^2.0.3"
"@nuxt/typescript-build": "^2.0.3",
"node-sass": "^4.14.1",
"sass-loader": "^10.0.4"
}
}

View File

@ -0,0 +1,45 @@
<template>
<div class="d-flex results align-content-stretch flex-wrap">
<scan-result-component v-for="(result, id) in data.results"
v-bind:key="id" v-bind:id="id" v-bind:result="result" />
</div>
</template>
<style>
.results {
margin: -0.5rem;
}
</style>
<script lang="ts">
import Vue from 'vue';
import ScanResult from '~/models/scan-result';
export default Vue.extend({
async asyncData({params, $axios}) {
return { data: await $axios.$get<ScanResult>(`results/${params.id}`) }
},
created() {
this.timer = setInterval(this.getData, 1000 * 5);
},
beforeDestroy() {
clearInterval(this.timer);
},
methods: {
async getData() {
this.data = await this.$axios.$get<ScanResult>(`results/${this.$route.params.id}`);
}
},
data() {
return {
data: null as ScanResult | null,
timer: 0 as any
}
}
});
</script>

View File

@ -1,4 +1,91 @@
<template>
<scan-form />
<b-card no-body>
<b-tabs card pills>
<b-tab title="File Scan" active>
<b-input-group>
<b-form-file placeholder="Select or drop files here" v-model="file"/>
<b-input-group-append>
<b-button variant="primary" :disabled="!file || uploading" @click="scanFile">Scan</b-button>
</b-input-group-append>
</b-input-group>
<b-progress class="mt-3" v-if="uploading" :value="progress" animated />
</b-tab>
<b-tab title="URL Scan">
<b-input-group>
<b-input type="url" placeholder="https://secure.eicar.org/eicar.com.txt" v-model="url"/>
<b-input-group-append>
<b-button variant="primary" :disabled="!isUrl(url)" @click="scanUrl">Scan</b-button>
</b-input-group-append>
</b-input-group>
</b-tab>
</b-tabs>
</b-card>
</template>
<script lang="ts">
import Vue from 'vue';
import ScanResult from '~/models/scan-result';
export default Vue.extend({
data() {
return {
uploading: false,
progress: 0,
file: null as File | null,
url: '' as string,
}
},
methods: {
isUrl(url: string) : boolean {
return /(http|https):\/\/(\w+:?\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@\-\/]))?/.test(url);
},
async scanFile() {
if (!this.file || this.uploading)
return
const data = new FormData();
data.append('file', this.file, this.file.name);
try
{
this.uploading = true;
const result = await this.$axios.$post<ScanResult>('queue/file', data, {
onUploadProgress: (progressEvent) => {
if (progressEvent.total == 0)
return;
this.progress = progressEvent.loaded / progressEvent.total * 100;
}
});
await this.$router.push({name: 'id', params: {id: result.id}});
}
finally {
this.progress = 0;
this.uploading = false;
}
},
async scanUrl() {
if (!this.isUrl(this.url))
return;
const data = new FormData();
data.append('url', this.url);
const result = await this.$axios.$post<ScanResult>('queue/url', data);
await this.$router.push({name: 'id', params: {id: result.id}});
}
}
});
</script>

View File

@ -25,7 +25,8 @@
},
"types": [
"@types/node",
"@nuxt/types"
"@nuxt/types",
"@nuxtjs/axios"
]
},
"exclude": [