start jobs builder + add table filter + overflow

* update python jobs builder (start merging in the builder format)
* update some header and keys to filter
* add filter table items logic on Filter component
* fix table overflow style update with filtering
* add i18n
This commit is contained in:
Jordan Blasenhauer 2024-06-18 11:17:18 +02:00
parent 79e5f8c8f2
commit 4e65e24292
8 changed files with 3013 additions and 2536 deletions

View file

@ -0,0 +1,91 @@
<script setup>
// Containers
import Grid from "@components/Widget/Grid.vue";
import GridLayout from "@components/Widget/GridLayout.vue";
import Table from "@components/Widget/Table.vue";
import Title from "@components/Widget/Title.vue";
/**
@name Builder/Jobs.vue
@description This component is lightweight builder containing only the necessary components to create the jobs page.
@example
[
{
"type": "card",
"containerColumns": {
"pc": 4,
"tablet": 6,
"mobile": 12
},
"widgets": [
{
"type": "table",
"data": {
"title": "jobs_table_title",
"minWidth": "lg",
"header": [
"jobs_table_name",
"jobs_table_plugin_id",
"jobs_table_interval",
"jobs_table_last_run",
"jobs_table_success",
"jobs_table_last_run_date",
"jobs_table_cache"
],
"positions": [
2,
2,
1,
1,
1,
3,
2
],
"items": [
[
{
"name": "anonymous-report",
"type": "Text",
"data": {
"text": "anonymous-report"
}
},
],
]
}
}
]
}
]
@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"
>
<!-- widget grid -->
<Grid>
<!-- widget element -->
<template v-for="(widget, index) in container.widgets" :key="index">
<Table v-if="widget.type === 'Table'" v-bind="widget.data" />
<Title v-if="widget.type === 'Title'" v-bind="widget.data" />
</template>
</Grid>
</GridLayout>
</template>

View file

@ -13,13 +13,14 @@ import { useFilter } from "@utils/form.js";
We have default values that avoid filter ("all" for select and "" for keyword).
Filters are fields so we need to provide a "field" key with same structure as a Field.
We have to define "keys" that will be the keys the filter value will check.
We can set filter :"default" in order to filter the base keys of an object.
We can set filter :"settings" in order to filter settings, data must be an advanced template.
We can set filter "default" in order to filter the base keys of an object.
We can set filter "settings" in order to filter settings, data must be an advanced template.
We can set filter "table" in order to filter table items.
Check example for more details.
@example
[
{
filter: "default", // or "settings"
filter: "default", // or "settings" or "table"
type: "select",
value: "all",
keys: ["type"],
@ -85,8 +86,6 @@ function filterData(filter, value) {
let template = JSON.parse(JSON.stringify(props.data));
const getFilters = JSON.parse(JSON.stringify(filters.base));
// Filter order for template : plugins, settings
// Base keys filtering (like plugin)
const defaultFilters = getFilters.filter((f) => f.filter === "default");
template = useFilter(template, defaultFilters);
@ -117,6 +116,28 @@ function filterData(filter, value) {
});
}
// Base keys filtering (like plugin)
const tableFilters = getFilters.filter((f) => f.filter === "table");
if (tableFilters.length) {
// Loop on each array of array
for (let i = 0; i < template.length; i++) {
const row = template[i];
// We need to check one complete row on filter
// So we need to merge all keys of each column
const mergeRow = {};
row.forEach((item) => {
Object.keys(item).forEach((key) => {
mergeRow[key] = item[key];
});
});
const newRow = useFilter([mergeRow], tableFilters);
// Case newRow is empty array, didn't pass filter
if (Array.isArray(newRow) && newRow.length <= 0) template[i] = [];
}
// Remove empty row
template = template.filter((row) => row.length > 0);
}
emits("filter", template);
}
</script>

View file

@ -1,5 +1,5 @@
<script setup>
import { reactive, computed, ref, onMounted, onUpdated } from "vue";
import { reactive, computed, ref, onMounted, watch } from "vue";
import { v4 as uuidv4 } from "uuid";
import Container from "@components/Widget/Container.vue";
import Text from "@components/Widget/Text.vue";
@ -33,7 +33,7 @@ import Filter from "@components/Widget/Filter.vue";
],
...
],
const filters = [
{
filter: "default",
@ -123,11 +123,12 @@ const tableHeader = ref(null);
const table = reactive({
id: uuidv4(),
overflow: "0px",
length: computed(() => {
return props.header.length;
}),
rowLength: computed(() => {
return props.items.length;
return table.itemsFormat.length;
}),
title: computed(() => {
return props.title ? props.title : "dashboard_table";
@ -139,22 +140,27 @@ const table = reactive({
});
function getOverflow() {
const overflow =
tableBody.value.getBoundingClientRect().width - tableBody.value.clientWidth;
setTimeout(() => {
const overflow =
+tableBody.value.getBoundingClientRect().width -
+tableBody.value.clientWidth;
if (overflow > 0) {
return (tableHeader.value.style.paddingRight = `${overflow}px`);
}
// remove style
return tableHeader.value.removeAttribute("style");
table.overflow = tableHeader.value.style.paddingRight = `${overflow}px`;
if (overflow <= 0) {
return tableHeader.value.removeAttribute("style");
}
}, 10);
}
onMounted(() => {
getOverflow();
});
// whatch itemsFormat changes
watch(
() => table.itemsFormat,
() => {
getOverflow();
}
);
onUpdated(() => {
onMounted(() => {
getOverflow();
});
</script>
@ -163,7 +169,7 @@ onUpdated(() => {
<Container :containerClass="`${props.containerClass} table-container`">
<Filter
v-if="filters.length"
@filter="(v) => (table.itemsFormat = filterData)"
@filter="(v) => (table.itemsFormat = v)"
:data="table.itemsBase"
:filters="filters"
/>
@ -190,20 +196,24 @@ onUpdated(() => {
:class="['table-header-item', `col-span-${props.positions[id]}`]"
>
<th role="columnheader">
{{ head }}
{{ $t(head, head) }}
</th>
</tr>
</thead>
<tbody data-table-body ref="tableBody" class="table-content">
<tr
v-for="rowId in table.rowLength - 1"
v-for="rowId in table.rowLength"
:key="rowId - 1"
role="row"
:aria-rowindex="rowId"
class="table-content-item"
>
<template v-for="(col, id) in table.itemsFormat[rowId]">
<template
v-for="(col, id) in table.itemsFormat[rowId - 1]"
:key="col"
>
<td
role="Scell"
role="cell"
:class="[
'table-content-item-wrap',
`col-span-${props.positions[id]}`,

View file

@ -180,6 +180,7 @@
"instances_status": "status",
"instances_active": "active",
"instances_inactive": "inactive",
"jobs_title": "Jobs list",
"jobs_download_cache_file": "download cache files",
"jobs_search": "search jobs",
"jobs_search_desc": "Search within job name, plugin id or last run.",
@ -187,6 +188,14 @@
"jobs_interval_desc": "Choose interval between available options.",
"jobs_success": "success",
"jobs_success_desc": "Choose the job success state.",
"jobs_last_run": "last run",
"jobs_last_run_desc": "Choose the job last run state."
"jobs_reload": "reload",
"jobs_reload_desc": "Choose the job reload state.",
"jobs_table_title" :"Jobs list with plugin id, name, last run, interval and success state.",
"jobs_table_name" : "Name",
"jobs_table_plugin_id" : "Plugin id",
"jobs_table_interval" : "Interval",
"jobs_table_reload" : "Reload",
"jobs_table_success" : "Success",
"jobs_table_last_run_date" : "Last run date",
"jobs_table_cache" : "Cache"
}

View file

@ -1,9 +1,9 @@
<script setup>
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderHome from "@components/Builder/Home.vue";
import { useGlobal } from "@utils/global.js";
import { useForm } from "@utils/form.js";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderHome from "@components/Builder/Home.vue";
/**
@name Page/Home.vue

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -205,7 +205,7 @@ jobs = {
}
def jobs_to_list(jobs):
def get_jobs_list(jobs):
data = []
# loop on each dict
for key, value in jobs.items():
@ -282,9 +282,156 @@ def jobs_to_list(jobs):
data.append(item)
# store on a file
return data
def job_builder(jobs):
jobs_list = get_jobs_list(jobs)
intervals = ["all"]
# loop on each job
for job in jobs_list:
# loop on each item
for item in job:
# get the interval if not already in intervals
if item.get("every") and item.get("every") not in intervals:
intervals.append(item.get("every"))
builder = [
{
"type": "card",
"containerColumns": {"pc": 12, "tablet": 12, "mobile": 12},
"widgets": [
{
"type": "Title",
"data": {"title": "jobs_title", "tag": "h1", "type": "card"},
},
{
"type": "Table",
"data": {
"title": "jobs_table_title",
"minWidth": "lg",
"header": [
"jobs_table_name",
"jobs_table_plugin_id",
"jobs_table_interval",
"jobs_table_reload",
"jobs_table_success",
"jobs_table_last_run_date",
"jobs_table_cache",
],
"positions": [2, 2, 1, 1, 1, 2, 3],
"items": jobs_list,
"filters": [
{
"filter": "table",
"filterName": "keyword",
"type": "keyword",
"value": "",
"keys": ["name", "plugin_id", "last_run"],
"field": {
"id": "jobs-keyword",
"value": "",
"type": "text",
"name": "jobs-keyword",
"containerClass": "setting",
"label": "jobs_search",
"placeholder": "inp_keyword",
"isClipboard": False,
"popovers": [
{
"text": "jobs_search_desc",
"iconName": "info",
"iconColor": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"filter": "table",
"filterName": "every",
"type": "select",
"value": "all",
"keys": ["every"],
"field": {
"id": "jobs-every",
"value": "all",
"values": intervals,
"name": "jobs-every",
"onlyDown": True,
"label": "jobs_interval",
"containerClass": "setting",
"popovers": [
{
"text": "jobs_interval_desc",
"iconName": "info",
"iconColor": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"filter": "table",
"filterName": "success",
"type": "select",
"value": "all",
"keys": ["success"],
"field": {
"id": "jobs-success",
"value": "all",
"values": ["all", "success", "failed"],
"name": "jobs-success",
"onlyDown": True,
"containerClass": "setting",
"label": "jobs_success",
"popovers": [
{
"text": "jobs_success_desc",
"iconName": "info",
"iconColor": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"filter": "table",
"filterName": "reload",
"type": "select",
"value": "all",
"keys": ["reload"],
"field": {
"id": "jobs-last-run",
"value": "all",
"values": ["all", "success", "failed"],
"name": "jobs-last-run",
"onlyDown": True,
"containerClass": "setting",
"label": "jobs_reload",
"popovers": [
{
"text": "jobs_reload_desc",
"iconName": "info",
"iconColor": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
],
},
},
],
}
]
# store on a file
with open("jobs.json", "w") as f:
json.dump(data, f, indent=4)
json.dump(builder, f, indent=4)
jobs_to_list(jobs)
job_builder(jobs)