fix key lang issue + start reports page

*add a workaround to avoid log error when the key send for the i18n is an interpolation literal or any invalid syntaxe
* start reports page :  format data as table list
This commit is contained in:
Jordan Blasenhauer 2024-06-25 16:07:31 +02:00
parent e0112c679b
commit c9ddcb4986
25 changed files with 1253 additions and 35 deletions

View file

@ -1594,7 +1594,7 @@ body {
}
.table-content-item-wrap {
@apply appearance-none pl-1;
@apply appearance-none pl-1 pr-2;
}
/** MISC **/

File diff suppressed because one or more lines are too long

View file

@ -8,7 +8,7 @@ import Text from "@components/Widget/Text.vue";
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
/**
@name Builder/pLugin.vue
@name Builder/PLugin.vue
@description This component is lightweight builder containing only the necessary components to create the plugins page.
@example
[

View file

@ -0,0 +1,56 @@
<script setup>
// Containers
import Grid from "@components/Widget/Grid.vue";
import GridLayout from "@components/Widget/GridLayout.vue";
import Title from "@components/Widget/Title.vue";
import Table from "@components/Widget/Table.vue";
import MessageUnmatch from "@components/Message/Unmatch.vue";
/**
@name Builder/Reports.vue
@description This component is lightweight builder containing only the necessary components to create the reports page.
@example
[
{
type: "card",
gridLayoutClass: "transparent",
widgets: [{ type: "MessageUnmatch", data: { text: "reports_not_found" } }],
},
];
@param {array} builder - Array of containers and widgets
*/
const props = defineProps({
builder: {
type: Array,
required: true,
},
});
</script>
<template>
<!-- top level grid (layout) -->
<GridLayout
v-for="(container, index) in props.builder"
:key="index"
:gridLayoutClass="container.containerClass"
:type="container.type"
:title="container.title"
:link="container.link"
:columns="container.containerColumns"
:id="container.id"
>
<!-- widget grid -->
<Grid>
<!-- widget element -->
<template v-for="(widget, index) in container.widgets" :key="index">
<MessageUnmatch
v-if="widget.type === 'MessageUnmatch'"
v-bind="widget.data"
/>
<Title v-if="widget.type === 'Title'" v-bind="widget.data" />
<Table v-if="widget.type === 'Table'" v-bind="widget.data" />
</template>
</Grid>
</GridLayout>
</template>

View file

@ -47,14 +47,30 @@ onMounted(() => {
<nav>
<!-- breadcrumb -->
<h2 class="header-title">
{{ $t(`dashboard_${header.currPath}`, header.currPath) }}
{{
$t(
`dashboard_${header.currPath}`,
$t(
"dashboard_placeholder",
$t("dashboard_placeholder", header.currPath)
)
)
}}
</h2>
<ul class="header-breadcrumb-container">
<li class="header-breadcrumb-item first">
{{ $t("dashboard_bw") }}
</li>
<li class="header-breadcrumb-item slash mobile active">
{{ $t(`dashboard_${header.lastPath}`, header.lastPath) }}
{{
$t(
`dashboard_${header.lastPath}`,
$t(
"dashboard_placeholder",
$t("dashboard_placeholder", header.lastPath)
)
)
}}
</li>
</ul>
</nav>

View file

@ -204,7 +204,12 @@ onMounted(() => {
:readonly="props.readonly || false"
:disabled="props.disabled || false"
:placeholder="
props.placeholder ? $t(props.placeholder, props.placeholder) : ''
props.placeholder
? $t(
props.placeholder,
$t('dashboard_placeholder', props.placeholder)
)
: ''
"
:pattern="props.pattern || '(?s).*'"
:name="props.name"

View file

@ -90,8 +90,8 @@ onMounted(() => {
>
{{
props.label
? $t(props.label, props.label)
: $t(props.name, props.name)
? $t(props.label, $t("dashboard_placeholder", props.label))
: $t(props.name, $t("dashboard_placeholder", props.name))
}}
</label>
<span v-if="props.required" class="input-header-required-sign">*</span>

View file

@ -32,10 +32,10 @@ const gridClass = computed(() => {
<ul data-is="list-pairs" v-if="props.pairs" :class="['list-pairs-container']">
<li v-for="item in props.pairs" :class="['list-pairs-item', gridClass]">
<span class="list-pairs-title">
{{ $t(item["key"], item["key"]) }}
{{ $t(item["key"], $t("dashboard_placeholder", item["key"])) }}
</span>
<span class="list-pairs-subtitle">
{{ $t(item["value"], item["value"]) }}
{{ $t(item["value"], $t("dashboard_placeholder", item["value"])) }}
</span>
</li>
</ul>

View file

@ -87,7 +87,7 @@ onMounted(() => {
<div :class="[props.type, 'feedback-alert-wrap bg-el']">
<div class="feedback-alert-header">
<h5 class="feedback-alert-title">
{{ $t(props.title, props.title) }}
{{ $t(props.title, $t("dashboard_placeholder", props.title)) }}
</h5>
<button
:tabindex="props.tabId"
@ -113,7 +113,9 @@ onMounted(() => {
</svg>
</button>
</div>
<p class="feedback-alert-text">{{ $t(props.message, props.message) }}</p>
<p class="feedback-alert-text">
{{ $t(props.message, $t("dashboard_placeholder", props.message)) }}
</p>
</div>
</div>
</template>

View file

@ -148,7 +148,7 @@ onMounted(() => {
'pointer-events-none',
]"
:id="`text-${btn.id}`"
>{{ $t(props.text, props.text) }}
>{{ $t(props.text, $t("dashboard_placeholder", props.text)) }}
</span>
<Icons
v-if="props.iconName"

View file

@ -191,7 +191,7 @@ onMounted(() => {
:aria-description="$t('dashboard_popover_detail_desc')"
>
<p :id="`${popover.id}-popover-text`" class="popover-text">
{{ $t(props.text, props.text) }}
{{ $t(props.text, $t("dashboard_placeholder", props.text)) }}
</p>
</div>
</template>

View file

@ -39,14 +39,10 @@ const status = reactive({
});
const statusDesc = computed(() => {
if (props.status === "success")
return ["dashboard_status_success", "status active or success."];
if (props.status === "error")
return ["dashboard_status_error", "status inactive or error."];
if (props.status === "warning")
return ["dashboard_status_warning", "status warning or alert."];
if (props.status === "info")
return ["dashboard_status_info", "status loading or waiting or unknown."];
if (props.status === "success") return "dashboard_status_success";
if (props.status === "error") return "dashboard_status_error";
if (props.status === "warning") return "dashboard_status_warning";
if (props.status === "info") return "dashboard_status_info";
});
onMounted(() => {
@ -61,7 +57,7 @@ onMounted(() => {
:class="[props.status, 'status-icon']"
></div>
<p :id="`status-${status.id}`" class="sr-only">
{{ $t(statusDesc[0], statusDesc[1]) }}
{{ $t(statusDesc) }}
</p>
</div>
</template>

View file

@ -74,6 +74,6 @@ onMounted(() => {
v-if="props.subtitle"
:class="[subtitle.class, props.color, 'text-el']"
>
{{ $t(props.subtitle, props.subtitle) }}
{{ $t(props.subtitle, $t("dashboard_placeholder", props.subtitle)) }}
</component>
</template>

View file

@ -203,7 +203,7 @@ onMounted(() => {
v-for="(head, id) in props.header"
:class="['table-header-item', `col-span-${props.positions[id]}`]"
>
{{ $t(head, head) }}
{{ $t(head, $t("dashboard_placeholder", head)) }}
</th>
</tr>
</thead>

View file

@ -78,7 +78,7 @@ onMounted(() => {
ref="textEl"
:class="[text.class, props.color, 'text-el']"
>
{{ $t(props.text, props.text) }}
{{ $t(props.text, $t("dashboard_placeholder", props.text)) }}
</component>
<div :class="['flex justify-center items-center']" v-if="props.icon">
@ -89,7 +89,7 @@ onMounted(() => {
v-bind="props.attrs"
:class="[text.class, props.color, 'text-el', 'ml-2']"
>
{{ $t(props.text, props.text) }}
{{ $t(props.text, $t("dashboard_placeholder", props.text)) }}
</component>
</div>
</template>

View file

@ -85,6 +85,6 @@ onMounted(() => {
v-if="props.title"
:class="[props.color, isSubtitleClass, title.class, 'text-el']"
>
{{ $t(props.title, props.title) }}
{{ $t(props.title, $t("dashboard_placeholder", props.title)) }}
</component>
</template>

View file

@ -1,4 +1,5 @@
{
"dashboard_placehoder" : "{placeholder}",
"dashboard_logo_alt": "BunkerWeb logo image",
"dashboard_logo_link_label": "Redirect to home page",
"dashboard_bw": "BunkerWeb",
@ -215,5 +216,27 @@
"plugins_type_desc": "Only show plugins of the chosen type",
"plugins_delete_desc": "Delete plugin",
"plugins_modal_delete_title": "Delete plugin",
"plugins_modal_delete_confirm": "Are you sure you want to delete the plugin below ?"
"plugins_modal_delete_confirm": "Are you sure you want to delete the plugin below ?",
"reports_not_found": "No reports found",
"reports_search": "Search reports",
"reports_search_desc": "Search within report date, ip, url, user agent or data.",
"reports_country": "Country",
"reports_country_desc": "Country are alpha-2 country code based.",
"reports_method": "Method",
"reports_method_desc": "Methods are HTTP methods.",
"reports_status": "Status",
"reports_status_desc": "Status are HTTP status codes.",
"reports_reason": "Reason",
"reports_reason_desc": "Reason is the plugin name that triggered the report.",
"reports_title" :"Reports",
"reports_table_title" :"Reports list with date, ip, country, method, url, status code, user agent, reason and data.",
"reports_table_date" : "Date",
"reports_table_ip": "IP",
"reports_table_country" : "Country",
"reports_table_method" : "Method",
"reports_table_url" : "URL",
"reports_table_status_code" : "Status code",
"reports_table_cache_user_agent" : "User agent",
"reports_table_reason" : "Reason",
"reports_table_data": "Data"
}

View file

@ -1,4 +1,5 @@
{
"dashboard_placeholder" : "{placeholder}",
"dashboard_logo_alt": "BunkerWeb logo image",
"dashboard_logo_link_label": "Redirect to home page",
"dashboard_bw": "BunkerWeb",

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Reports</title>
</head>
<body>
<div class="hidden" data-server-global='{"username" : "admin"}'></div>
<div class="hidden"
data-server-flash='[{"type" : "success", "title" : "title", "message" : "Success feedback"}, {"type" : "error", "title" : "title", "message" : "Error feedback"}, {"type" : "warning", "title" : "title", "message" : "Warning feedback"}, {"type" : "info", "title" : "title", "message" : "Info feedback"}]'>
</div>
<div class="hidden"
data-server-builder='[{"type":"card","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"bunkerweb"},{"key":"instances_type","value":"manual"},{"key":"instances_status","value":"instances_active"}],"status":"success","title":"bunkerweb","buttons":[{"attrs":{"data-form-INSTANCE_ID":"bunkerweb","data-form-operation":"reload","data-submit-form":"true"},"text":"action_reload","color":"warning","size":"normal"},{"attrs":{"data-form-INSTANCE_ID":"bunkerweb","data-form-operation":"stop","data-submit-form":"true"},"text":"action_stop","color":"error","size":"normal"}]}}]}]'>
</div>
<div id="app"></div>
<script type="module" src="reports.js"></script>
</body>
</html>

View file

@ -0,0 +1,11 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import { getI18n } from "@utils/lang.js";
import Reports from "./reports.vue";
const pinia = createPinia();
createApp(Reports)
.use(pinia)
.use(getI18n(["dashboard", "action", "inp", "icons", "reports"]))
.mount("#app");

View file

@ -0,0 +1,405 @@
<script setup>
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderReports from "@components/Builder/Reports.vue";
import { useGlobal } from "@utils/global.js";
import { useForm } from "@utils/form.js";
/**
@name Page/Reports.vue
@description This component is the report page.
This page displays global information about reports, and allow to delete or upload some reports.
*/
const reports = reactive({
builder: "",
});
onBeforeMount(() => {
// Get builder data
const dataAtt = "data-server-builder";
const dataEl = document.querySelector(`[${dataAtt}]`);
const data =
dataEl && !dataEl.getAttribute(dataAtt).includes(dataAtt)
? JSON.parse(dataEl.getAttribute(dataAtt))
: {};
reports.builder = data;
});
onMounted(() => {
useGlobal();
useForm();
});
const builder = [
{
type: "card",
containerColumns: {
pc: 12,
tablet: 12,
mobile: 12,
},
widgets: [
{
type: "Title",
data: {
title: "reports_title",
},
},
{
type: "Table",
data: {
title: "reports_table_title",
minWidth: "lg",
header: [
"reports_table_date",
"reports_table_ip",
"reports_table_country",
"reports_table_method",
"reports_table_url",
"reports_table_status_code",
"reports_table_cache_user_agent",
"reports_table_reason",
"reports_table_data",
],
positions: [1, 1, 1, 1, 2, 1, 2, 1, 2],
items: [
[
{
date: "25/06/2024 07:40:23",
type: "Text",
data: {
text: "25/06/2024 07:40:23",
},
},
{
ip: "172.21.0.1",
type: "Text",
data: {
text: "172.21.0.1",
},
},
{
country: "local",
type: "Text",
data: {
text: "local",
},
},
{
method: "GET",
type: "Text",
data: {
text: "GET",
},
},
{
url: "/admin/login?id=etc/passwd",
type: "Text",
data: {
text: "/admin/login?id=etc/passwd",
},
},
{
code: "403",
type: "Text",
data: {
text: "403",
},
},
{
user_agent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
type: "Text",
data: {
text: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
},
},
{
reason: "modsecurity",
type: "Text",
data: {
text: "modsecurity",
},
},
{
data: {
text: '{"fesfesfsefesfesfesfesfesfesfesfesfesfsefes": "fesfs"}',
},
type: "Text",
},
],
[
{
date: "25/06/2024 07:40:23",
type: "Text",
data: {
text: "25/06/2024 07:40:23",
},
},
{
ip: "111111",
type: "Text",
data: {
text: "111111",
},
},
{
country: "fr",
type: "Text",
data: {
text: "fr",
},
},
{
method: "POST",
type: "Text",
data: {
text: "POST",
},
},
{
url: "/admin/login?id=e",
type: "Text",
data: {
text: "/admin/login?id=e",
},
},
{
code: "403",
type: "Text",
data: {
text: "403",
},
},
{
user_agent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
type: "Text",
data: {
text: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
},
},
{
reason: " antibot",
type: "Text",
data: {
text: " antibot",
},
},
{
data: {
text: "{}",
},
type: "Text",
},
],
[
{
date: "25/06/2024 07:40:23",
type: "Text",
data: {
text: "25/06/2024 07:40:23",
},
},
{
ip: "111111",
type: "Text",
data: {
text: "111111",
},
},
{
country: "fr",
type: "Text",
data: {
text: "fr",
},
},
{
method: "POST",
type: "Text",
data: {
text: "POST",
},
},
{
url: "/admin/login?id=e",
type: "Text",
data: {
text: "/admin/login?id=e",
},
},
{
code: "403",
type: "Text",
data: {
text: "403",
},
},
{
user_agent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
type: "Text",
data: {
text: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
},
},
{
reason: " antibot",
type: "Text",
data: {
text: " antibot",
},
},
{
data: {
text: "{}",
},
type: "Text",
},
],
[
{
date: "25/06/2024 07:40:23",
type: "Text",
data: {
text: "25/06/2024 07:40:23",
},
},
{
ip: "111111",
type: "Text",
data: {
text: "111111",
},
},
{
country: "fr",
type: "Text",
data: {
text: "fr",
},
},
{
method: "POST",
type: "Text",
data: {
text: "POST",
},
},
{
url: "/admin/login?id=e",
type: "Text",
data: {
text: "/admin/login?id=e",
},
},
{
code: "403",
type: "Text",
data: {
text: "403",
},
},
{
user_agent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
type: "Text",
data: {
text: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
},
},
{
reason: " antibot",
type: "Text",
data: {
text: " antibot",
},
},
{
data: {
text: "{}",
},
type: "Text",
},
],
[
{
date: "25/06/2024 07:40:23",
type: "Text",
data: {
text: "25/06/2024 07:40:23",
},
},
{
ip: "111111",
type: "Text",
data: {
text: "111111",
},
},
{
country: "fr",
type: "Text",
data: {
text: "fr",
},
},
{
method: "POST",
type: "Text",
data: {
text: "POST",
},
},
{
url: "/admin/login?id=e",
type: "Text",
data: {
text: "/admin/login?id=e",
},
},
{
code: "403",
type: "Text",
data: {
text: "403",
},
},
{
user_agent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
type: "Text",
data: {
text: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
},
},
{
reason: " antibot",
type: "Text",
data: {
text: " antibot",
},
},
{
data: {
text: "{}",
},
type: "Text",
},
],
],
filters: [],
},
},
],
},
];
</script>
<template>
<DashboardLayout>
<BuilderReports v-if="builder" :builder="builder" />
</DashboardLayout>
</template>

View file

@ -1,4 +1,5 @@
import { createI18n } from "vue-i18n";
import en from "@lang/en.json" assert { type: "json" };
import fr from "@lang/fr.json" assert { type: "json" };
@ -59,8 +60,12 @@ function getI18n(pagesArr = []) {
availableLocales: availablesLangs,
fallbackWarn: false,
missingWarn: false,
warnHtmlMessage: false
});
warnHtmlMessage: false,
silentTranslationWarn: false,
missingWarn: false,
WarnHtmlInMessageLevel: "off",
messageCompiler: false,
});
return i18n;
}

View file

@ -334,7 +334,6 @@ def job_builder(jobs):
"value": "",
"type": "text",
"name": "jobs-keyword",
"containerClass": "setting",
"label": "jobs_search",
"placeholder": "inp_keyword",
"isClipboard": False,
@ -360,7 +359,6 @@ def job_builder(jobs):
"name": "jobs-every",
"onlyDown": True,
"label": "jobs_interval",
"containerClass": "setting",
"popovers": [
{
"text": "jobs_interval_desc",
@ -382,7 +380,6 @@ def job_builder(jobs):
"values": ["all", "success", "failed"],
"name": "jobs-last-run",
"onlyDown": True,
"containerClass": "setting",
"label": "jobs_reload",
"popovers": [
{
@ -405,7 +402,6 @@ def job_builder(jobs):
"values": ["all", "success", "failed"],
"name": "jobs-success",
"onlyDown": True,
"containerClass": "setting",
"label": "jobs_success",
"popovers": [
{

370
vuejs/tests/reports.json Normal file
View file

@ -0,0 +1,370 @@
[
{
"type": "card",
"containerColumns": {
"pc": 12,
"tablet": 12,
"mobile": 12
},
"widgets": [
{
"type": "Title",
"data": {
"title": "reports_title"
}
},
{
"type": "Table",
"data": {
"title": "reports_table_title",
"minWidth": "lg",
"header": [
"reports_table_date",
"reports_table_ip",
"reports_table_country",
"reports_table_method",
"reports_table_url",
"reports_table_status_code",
"reports_table_cache_user_agent",
"reports_table_reason",
"reports_table_data"
],
"positions": [
1,
1,
1,
1,
2,
1,
2,
1,
2
],
"items": [
[
{
"date": "25/06/2024 07:40:23",
"type": "Text",
"data": {
"text": "25/06/2024 07:40:23"
}
},
{
"ip": "172.21.0.1",
"type": "Text",
"data": {
"text": "172.21.0.1"
}
},
{
"country": "local",
"type": "Text",
"data": {
"text": "local"
}
},
{
"method": "GET",
"type": "Text",
"data": {
"text": "GET"
}
},
{
"url": "/admin/login?id=etc/passwd",
"type": "Text",
"data": {
"text": "/admin/login?id=etc/passwd"
}
},
{
"code": "403",
"type": "Text",
"data": {
"text": "403"
}
},
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"type": "Text",
"data": {
"text": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
},
{
"reason": "modsecurity",
"type": "Text",
"data": {
"text": "modsecurity"
}
},
{
"data": {
"text": "{\"fesfesfsefesfesfesfesfesfesfesfesfesfsefes\": \"fesfs\"}"
},
"type": "Text"
}
],
[
{
"date": "25/06/2024 07:40:23",
"type": "Text",
"data": {
"text": "25/06/2024 07:40:23"
}
},
{
"ip": "111111",
"type": "Text",
"data": {
"text": "111111"
}
},
{
"country": "fr",
"type": "Text",
"data": {
"text": "fr"
}
},
{
"method": "POST",
"type": "Text",
"data": {
"text": "POST"
}
},
{
"url": "/admin/login?id=e",
"type": "Text",
"data": {
"text": "/admin/login?id=e"
}
},
{
"code": "403",
"type": "Text",
"data": {
"text": "403"
}
},
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"type": "Text",
"data": {
"text": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
},
{
"reason": " antibot",
"type": "Text",
"data": {
"text": " antibot"
}
},
{
"data": {
"text": "{}"
},
"type": "Text"
}
],
[
{
"date": "25/06/2024 07:40:23",
"type": "Text",
"data": {
"text": "25/06/2024 07:40:23"
}
},
{
"ip": "111111",
"type": "Text",
"data": {
"text": "111111"
}
},
{
"country": "fr",
"type": "Text",
"data": {
"text": "fr"
}
},
{
"method": "POST",
"type": "Text",
"data": {
"text": "POST"
}
},
{
"url": "/admin/login?id=e",
"type": "Text",
"data": {
"text": "/admin/login?id=e"
}
},
{
"code": "403",
"type": "Text",
"data": {
"text": "403"
}
},
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"type": "Text",
"data": {
"text": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
},
{
"reason": " antibot",
"type": "Text",
"data": {
"text": " antibot"
}
},
{
"data": {
"text": "{}"
},
"type": "Text"
}
],
[
{
"date": "25/06/2024 07:40:23",
"type": "Text",
"data": {
"text": "25/06/2024 07:40:23"
}
},
{
"ip": "111111",
"type": "Text",
"data": {
"text": "111111"
}
},
{
"country": "fr",
"type": "Text",
"data": {
"text": "fr"
}
},
{
"method": "POST",
"type": "Text",
"data": {
"text": "POST"
}
},
{
"url": "/admin/login?id=e",
"type": "Text",
"data": {
"text": "/admin/login?id=e"
}
},
{
"code": "403",
"type": "Text",
"data": {
"text": "403"
}
},
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"type": "Text",
"data": {
"text": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
},
{
"reason": " antibot",
"type": "Text",
"data": {
"text": " antibot"
}
},
{
"data": {
"text": "{}"
},
"type": "Text"
}
],
[
{
"date": "25/06/2024 07:40:23",
"type": "Text",
"data": {
"text": "25/06/2024 07:40:23"
}
},
{
"ip": "111111",
"type": "Text",
"data": {
"text": "111111"
}
},
{
"country": "fr",
"type": "Text",
"data": {
"text": "fr"
}
},
{
"method": "POST",
"type": "Text",
"data": {
"text": "POST"
}
},
{
"url": "/admin/login?id=e",
"type": "Text",
"data": {
"text": "/admin/login?id=e"
}
},
{
"code": "403",
"type": "Text",
"data": {
"text": "403"
}
},
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"type": "Text",
"data": {
"text": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
},
{
"reason": " antibot",
"type": "Text",
"data": {
"text": " antibot"
}
},
{
"data": {
"text": "{}"
},
"type": "Text"
}
]
],
"filters": []
}
}
]
}
]

310
vuejs/tests/reports.py Normal file
View file

@ -0,0 +1,310 @@
import json
reports = [
{
"url": "/admin/login?id=etc/passwd",
"ip": "172.21.0.1",
"reason": "modsecurity",
"country": "local",
"status": 403,
"method": "GET",
"date": "25/06/2024 07:40:23",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"data": {"fesfesfsefesfesfesfesfesfesfesfesfesfsefes": "fesfs"},
},
{
"url": "/admin/login?id=e",
"ip": "111111",
"reason": " antibot",
"country": "fr",
"status": 403,
"method": "POST",
"date": "25/06/2024 07:40:23",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"data": {},
},
{
"url": "/admin/login?id=e",
"ip": "111111",
"reason": " antibot",
"country": "fr",
"status": 403,
"method": "POST",
"date": "25/06/2024 07:40:23",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"data": {},
},
{
"url": "/admin/login?id=e",
"ip": "111111",
"reason": " antibot",
"country": "fr",
"status": 403,
"method": "POST",
"date": "25/06/2024 07:40:23",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"data": {},
},
{
"url": "/admin/login?id=e",
"ip": "111111",
"reason": " antibot",
"country": "fr",
"status": 403,
"method": "POST",
"date": "25/06/2024 07:40:23",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"data": {},
},
]
# Reoder reports to get in order "date", "ip", "country", "method", "url", "code", "user_agent", "reason", "data"
for report in reports:
report["date"] = report.pop("date")
report["ip"] = report.pop("ip")
report["country"] = report.pop("country")
report["method"] = report.pop("method")
report["url"] = report.pop("url")
report["code"] = report.pop("status")
report["user_agent"] = report.pop("user_agent")
report["reason"] = report.pop("reason")
report["data"] = report.pop("data")
def get_reports_filter(reports):
if len(reports) <= 5:
return []
total_countries = ["all"]
total_methods = ["all"]
total_status = ["all"]
total_reasons = ["all"]
for report in reports:
if report.get("country") and report.get("country") not in total_countries:
total_countries.append(report.get("country"))
if report.get("method") and report.get("method") not in total_methods:
total_methods.append(report.get("method"))
if report.get("status") and report.get("status") not in total_status:
total_status.append(str(report.get("status")))
if report.get("reason") and report.get("reason") not in total_reasons:
total_reasons.append(report.get("reason"))
filters = []
filters.append(
{
"filter": "table",
"filterName": "keyword",
"type": "keyword",
"value": "",
"keys": ["url", "ip", "date", "user_agent"],
"field": {
"id": "reports-keyword",
"value": "",
"type": "text",
"name": "reports-keyword",
"label": "reports_search",
"placeholder": "inp_keyword",
"isClipboard": False,
"popovers": [
{
"text": "reports_search_desc",
"iconName": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
)
if len(total_countries) > 1:
filters.append(
{
"filter": "table",
"filterName": "country",
"type": "select",
"value": "all",
"keys": ["country"],
"field": {
"id": "reports-country",
"value": "all",
"values": total_countries,
"name": "reports-country",
"onlyDown": True,
"label": "reports_country",
"popovers": [
{
"text": "reports_country_desc",
"iconName": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
)
if len(total_methods) > 1:
filters.append(
{
"filter": "table",
"filterName": "method",
"type": "select",
"value": "all",
"keys": ["method"],
"field": {
"id": "reports-method",
"value": "all",
"values": total_methods,
"name": "reports-method",
"onlyDown": True,
"label": "reports_method",
"popovers": [
{
"text": "reports_method_desc",
"iconName": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
)
if len(total_status) > 1:
filters.append(
{
"filter": "table",
"filterName": "status",
"type": "select",
"value": "all",
"keys": ["status"],
"field": {
"id": "reports-status",
"value": "all",
"values": total_status,
"name": "reports-status",
"onlyDown": True,
"label": "reports_status",
"popovers": [
{
"text": "reports_status_desc",
"iconName": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
)
if len(total_reasons) > 1:
filters.append(
{
"filter": "table",
"filterName": "reason",
"type": "select",
"value": "all",
"keys": ["reason"],
"field": {
"id": "reports-reason",
"value": "all",
"values": total_reasons,
"name": "reports-reason",
"onlyDown": True,
"label": "reports_reason",
"popovers": [
{
"text": "reports_reason_desc",
"iconName": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
)
return filters
def get_reports_list(reports):
data = []
# loop on each dict
for report in reports:
item = []
for k, v in report.items():
item.append(
{
k: json.dumps(v) if isinstance(v, dict) else str(v),
"type": "Text",
"data": {
"text": json.dumps(v) if isinstance(v, dict) else str(v),
},
}
)
data.append(item)
return data
def reports_builder(reports, data=None):
if not reports:
return [
{
"type": "void",
"widgets": [
{"type": "MessageUnmatch", "data": {"text": "reports_not_found"}}
],
},
]
filters = get_reports_filter(reports)
reports_list = get_reports_list(reports)
builder = [
{
"type": "card",
"containerColumns": {"pc": 12, "tablet": 12, "mobile": 12},
"widgets": [
{
"type": "Title",
"data": {"title": "reports_title"},
},
{
"type": "Table",
"data": {
"title": "reports_table_title",
"minWidth": "lg",
"header": [
"reports_table_date",
"reports_table_ip",
"reports_table_country",
"reports_table_method",
"reports_table_url",
"reports_table_status_code",
"reports_table_cache_user_agent",
"reports_table_reason",
"reports_table_data",
],
"positions": [1, 1, 1, 1, 2, 1, 2, 1, 2],
"items": reports_list,
"filters": filters,
},
},
],
}
]
return builder
output = reports_builder(reports)
# store on a file
with open("reports.json", "w") as f:
json.dump(output, f, indent=4)