mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 02:14:38 -05:00
fix: responsive dashboard grid (#12608)
This commit is contained in:
committed by
GitHub
parent
7ba29e593f
commit
f7e3d1e6c5
@@ -28,33 +28,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadChart(chart: any) {
|
||||
const yamlChart = YAML_UTILS.stringify(chart);
|
||||
const result: { error: string | null; data: null | {
|
||||
id?: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
chartOptions?: Record<string, any>;
|
||||
dataFilters?: any[];
|
||||
charts?: any[];
|
||||
}; raw: any } = {
|
||||
error: null,
|
||||
data: null,
|
||||
raw: {}
|
||||
};
|
||||
const errors = await dashboardStore.validateChart(yamlChart);
|
||||
if (errors.constraints) {
|
||||
result.error = errors.constraints;
|
||||
} else {
|
||||
result.data = {...chart, content: yamlChart, raw: chart};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function updateChartPreview(event: any) {
|
||||
const chart = YAML_UTILS.getChartAtPosition(event.model.getValue(), event.position);
|
||||
if (chart) {
|
||||
const result = await loadChart(chart);
|
||||
const result = await dashboardStore.loadChart(chart);
|
||||
dashboardStore.selectedChart = typeof result.data === "object"
|
||||
? {
|
||||
...result.data,
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
FIELDNAME_INJECTION_KEY,
|
||||
FULL_SCHEMA_INJECTION_KEY,
|
||||
FULL_SOURCE_INJECTION_KEY,
|
||||
ON_TASK_EDITOR_CLICK_INJECTION_KEY,
|
||||
PARENT_PATH_INJECTION_KEY,
|
||||
POSITION_INJECTION_KEY,
|
||||
REF_PATH_INJECTION_KEY,
|
||||
@@ -111,6 +112,15 @@
|
||||
provide(BLOCK_SCHEMA_PATH_INJECTION_KEY, computed(() => props.blockSchemaPath ?? dashboardStore.schema.$ref ?? ""));
|
||||
provide(FULL_SOURCE_INJECTION_KEY, computed(() => dashboardStore.sourceCode ?? ""));
|
||||
provide(POSITION_INJECTION_KEY, props.position ?? "after");
|
||||
provide(ON_TASK_EDITOR_CLICK_INJECTION_KEY, (elt) => {
|
||||
const type = elt?.type;
|
||||
dashboardStore.loadChart(elt);
|
||||
if(type){
|
||||
pluginsStore.updateDocumentation({type});
|
||||
}else{
|
||||
pluginsStore.updateDocumentation();
|
||||
}
|
||||
})
|
||||
|
||||
const pluginsStore = usePluginsStore();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="w-100 p-4">
|
||||
<Sections
|
||||
:key="dashboardStore.sourceCode"
|
||||
:dashboard="{id: 'default', charts: []}"
|
||||
:charts="charts.map(chart => chart.data).filter(chart => chart !== null)"
|
||||
showDefault
|
||||
@@ -9,11 +10,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {ref, watch} from "vue";
|
||||
import Sections from "../sections/Sections.vue";
|
||||
import {Chart} from "../composables/useDashboards";
|
||||
import {useDashboardStore} from "../../../stores/dashboard";
|
||||
import * as YAML_UTILS from "@kestra-io/ui-libs/flow-yaml-utils";
|
||||
import throttle from "lodash/throttle";
|
||||
|
||||
interface Result {
|
||||
error: string[] | null;
|
||||
@@ -23,21 +25,27 @@
|
||||
|
||||
const charts = ref<Result[]>([])
|
||||
|
||||
onMounted(async () => {
|
||||
validateAndLoadAllCharts();
|
||||
});
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
function validateAndLoadAllCharts() {
|
||||
charts.value = [];
|
||||
const validateAndLoadAllChartsThrottled = throttle(validateAndLoadAllCharts, 500);
|
||||
|
||||
async function validateAndLoadAllCharts() {
|
||||
const allCharts = YAML_UTILS.getAllCharts(dashboardStore.sourceCode) ?? [];
|
||||
allCharts.forEach(async (chart: any) => {
|
||||
const loadedChart = await loadChart(chart);
|
||||
charts.value.push(loadedChart);
|
||||
});
|
||||
charts.value = await Promise.all(allCharts.map(async (chart: any) => {
|
||||
return loadChart(chart);
|
||||
}));
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dashboardStore.sourceCode,
|
||||
() => {
|
||||
validateAndLoadAllChartsThrottled();
|
||||
}
|
||||
, {immediate: true}
|
||||
);
|
||||
|
||||
|
||||
|
||||
async function loadChart(chart: any) {
|
||||
const yamlChart = YAML_UTILS.stringify(chart);
|
||||
const result: Result = {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<section id="charts" :class="{padding}">
|
||||
<el-row :gutter="16">
|
||||
<el-col
|
||||
<div class="dashboard-sections-container">
|
||||
<section id="charts" :class="{padding}">
|
||||
<div
|
||||
v-for="chart in props.charts"
|
||||
:key="`chart__${chart.id}`"
|
||||
:xs="24"
|
||||
:sm="(chart.chartOptions?.width || 6) * 4"
|
||||
:md="(chart.chartOptions?.width || 6) * 2"
|
||||
class="dashboard-block"
|
||||
:class="{
|
||||
[`dash-width-${chart.chartOptions?.width || 6}`]: true
|
||||
}"
|
||||
>
|
||||
<div class="d-flex flex-column">
|
||||
<div class="d-flex justify-content-between">
|
||||
@@ -64,9 +65,9 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -133,14 +134,28 @@
|
||||
<style scoped lang="scss">
|
||||
@import "@kestra-io/ui-libs/src/scss/variables";
|
||||
|
||||
.dashboard-sections-container{
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
$smallMobile: 375px;
|
||||
$tablet: 768px;
|
||||
|
||||
section#charts {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
@container (min-width: #{$smallMobile}) {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
@container (min-width: #{$tablet}) {
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
}
|
||||
&.padding {
|
||||
padding: 0 2rem 1rem;
|
||||
}
|
||||
|
||||
& .el-row .el-col {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.dashboard-block {
|
||||
& > div {
|
||||
height: 100%;
|
||||
padding: 1.5rem;
|
||||
@@ -159,5 +174,24 @@ section#charts {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dash-width-3, .dash-width-6, .dash-width-9, .dash-width-12 {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
@container (min-width: #{$smallMobile}) {
|
||||
.dash-width-6, .dash-width-9, .dash-width-12 {
|
||||
grid-column: span 6;
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: #{$tablet}) {
|
||||
.dash-width-9 {
|
||||
grid-column: span 9;
|
||||
}
|
||||
.dash-width-12 {
|
||||
grid-column: span 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -39,7 +39,7 @@ export const decodeSearchParams = (query: LocationQuery) =>
|
||||
operation
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
.filter(v => v !== null);
|
||||
|
||||
type Filter = Pick<AppliedFilter, "key" | "comparator" | "value">;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="playgroundStore.enabled && isTask && taskObject?.id" class="flow-playground">
|
||||
<PlaygroundRunTaskButton :taskId="taskObject?.id" />
|
||||
<div v-if="playgroundStore.enabled && isTask && taskModel?.id" class="flow-playground">
|
||||
<PlaygroundRunTaskButton :taskId="taskModel?.id" />
|
||||
</div>
|
||||
<el-form v-if="isTaskDefinitionBasedOnType" labelPosition="top">
|
||||
<el-form-item>
|
||||
@@ -17,12 +17,12 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div @click="isPlugin && pluginsStore.updateDocumentation(taskObject as Parameters<typeof pluginsStore.updateDocumentation>[0])">
|
||||
<div @click="() => onTaskEditorClick(taskModel)">
|
||||
<TaskObject
|
||||
v-loading="isLoading"
|
||||
v-if="(selectedTaskType || !isTaskDefinitionBasedOnType) && schema"
|
||||
name="root"
|
||||
:modelValue="taskObject"
|
||||
:modelValue="taskModel"
|
||||
@update:model-value="onTaskInput"
|
||||
:schema
|
||||
:properties
|
||||
@@ -43,6 +43,7 @@
|
||||
FULL_SCHEMA_INJECTION_KEY,
|
||||
SCHEMA_DEFINITIONS_INJECTION_KEY,
|
||||
DATA_TYPES_MAP_INJECTION_KEY,
|
||||
ON_TASK_EDITOR_CLICK_INJECTION_KEY,
|
||||
} from "../injectionKeys";
|
||||
import {removeNullAndUndefined} from "../utils/cleanUp";
|
||||
import {removeRefPrefix, usePluginsStore} from "../../../stores/plugins";
|
||||
@@ -63,9 +64,9 @@
|
||||
const pluginsStore = usePluginsStore();
|
||||
const playgroundStore = usePlaygroundStore();
|
||||
|
||||
type PartialCodeElement = Partial<NoCodeElement>;
|
||||
type PartialNoCodeElement = Partial<NoCodeElement>;
|
||||
|
||||
const taskObject = ref<PartialCodeElement | undefined>({});
|
||||
const taskModel = ref<PartialNoCodeElement | undefined>({});
|
||||
const selectedTaskType = ref<string>();
|
||||
const isLoading = ref(false);
|
||||
|
||||
@@ -108,7 +109,7 @@
|
||||
|
||||
watch(modelValue, (v) => {
|
||||
if (!v) {
|
||||
taskObject.value = {};
|
||||
taskModel.value = {};
|
||||
selectedTaskType.value = undefined;
|
||||
} else {
|
||||
setup()
|
||||
@@ -150,20 +151,20 @@
|
||||
});
|
||||
|
||||
function setup() {
|
||||
const parsed = YAML_UTILS.parse<PartialCodeElement>(modelValue.value);
|
||||
const parsed = YAML_UTILS.parse<PartialNoCodeElement>(modelValue.value);
|
||||
if(isPluginDefaults.value){
|
||||
const {forced, type, values} = parsed as any;
|
||||
taskObject.value = {...values, forced, type};
|
||||
taskModel.value = {...values, forced, type};
|
||||
}else{
|
||||
taskObject.value = parsed;
|
||||
taskModel.value = parsed;
|
||||
}
|
||||
selectedTaskType.value = taskObject.value?.type;
|
||||
selectedTaskType.value = taskModel.value?.type;
|
||||
}
|
||||
|
||||
// when tab is opened, load the documentation
|
||||
onActivated(() => {
|
||||
if(selectedTaskType.value && parentPath !== "inputs"){
|
||||
pluginsStore.updateDocumentation(taskObject.value as Parameters<typeof pluginsStore.updateDocumentation>[0]);
|
||||
pluginsStore.updateDocumentation(taskModel.value as Parameters<typeof pluginsStore.updateDocumentation>[0]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -218,7 +219,7 @@
|
||||
const resolvedType = computed<string>(() => {
|
||||
if(resolvedTypes.value.length > 1 && selectedTaskType.value){
|
||||
// find the resolvedType that match the current dataType
|
||||
const dataType = taskObject.value?.data?.type;
|
||||
const dataType = taskModel.value?.data?.type;
|
||||
if(dataType){
|
||||
for(const typeLocal of resolvedTypes.value){
|
||||
const schema = definitions.value?.[typeLocal];
|
||||
@@ -330,13 +331,13 @@
|
||||
watch([selectedTaskType, fullSchema], ([task]) => {
|
||||
if (task) {
|
||||
if(isPlugin.value){
|
||||
pluginsStore.updateDocumentation(taskObject.value as Parameters<typeof pluginsStore.updateDocumentation>[0]);
|
||||
pluginsStore.updateDocumentation(taskModel.value as Parameters<typeof pluginsStore.updateDocumentation>[0]);
|
||||
}
|
||||
}
|
||||
}, {immediate: true});
|
||||
|
||||
function onTaskInput(val: PartialCodeElement | undefined) {
|
||||
taskObject.value = val;
|
||||
function onTaskInput(val: PartialNoCodeElement | undefined) {
|
||||
taskModel.value = val;
|
||||
if(fieldName){
|
||||
val = {
|
||||
[fieldName]: val,
|
||||
@@ -362,12 +363,21 @@
|
||||
}
|
||||
|
||||
function onTaskTypeSelect() {
|
||||
const value: PartialCodeElement = {
|
||||
const value: PartialNoCodeElement = {
|
||||
type: selectedTaskType.value ?? ""
|
||||
};
|
||||
|
||||
onTaskInput(value);
|
||||
}
|
||||
|
||||
const onTaskEditorClick = inject(ON_TASK_EDITOR_CLICK_INJECTION_KEY, (elt?: PartialNoCodeElement) => {
|
||||
const type = elt?.type;
|
||||
if(isPlugin.value && type){
|
||||
pluginsStore.updateDocumentation({type});
|
||||
}else{
|
||||
pluginsStore.updateDocumentation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type {ComputedRef, InjectionKey, Ref} from "vue"
|
||||
import {TopologyClickParams} from "./utils/types"
|
||||
import {NoCodeElement, TopologyClickParams} from "./utils/types"
|
||||
import {Panel} from "../../utils/multiPanelTypes"
|
||||
|
||||
export const BLOCK_SCHEMA_PATH_INJECTION_KEY = Symbol("block-schema-path-injection-key") as InjectionKey<ComputedRef<string>>
|
||||
@@ -90,4 +90,6 @@ export const FULL_SCHEMA_INJECTION_KEY = Symbol("full-schema-injection-key") as
|
||||
|
||||
export const SCHEMA_DEFINITIONS_INJECTION_KEY = Symbol("schema-definitions-injection-key") as InjectionKey<ComputedRef<Record<string, any>>>
|
||||
|
||||
export const DATA_TYPES_MAP_INJECTION_KEY = Symbol("data-types-injection-key") as InjectionKey<ComputedRef<Record<string, string[] | undefined>>>
|
||||
export const DATA_TYPES_MAP_INJECTION_KEY = Symbol("data-types-injection-key") as InjectionKey<ComputedRef<Record<string, string[] | undefined>>>
|
||||
|
||||
export const ON_TASK_EDITOR_CLICK_INJECTION_KEY = Symbol("on-task-editor-click-injection-key") as InjectionKey<(elt?: Partial<NoCodeElement>) => void>;
|
||||
@@ -140,6 +140,49 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
||||
return rootSchema.value?.properties;
|
||||
});
|
||||
|
||||
async function loadChart(chart: any) {
|
||||
const yamlChart = YAML_UTILS.stringify(chart);
|
||||
if(selectedChart.value?.content === yamlChart){
|
||||
return {
|
||||
error: chartErrors.value.length > 0 ? chartErrors.value[0] : null,
|
||||
data: selectedChart.value ? {...selectedChart.value, raw: chart} : null,
|
||||
raw: chart
|
||||
};
|
||||
}
|
||||
const result: { error: string | null; data: null | {
|
||||
id?: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
chartOptions?: Record<string, any>;
|
||||
dataFilters?: any[];
|
||||
charts?: any[];
|
||||
}; raw: any } = {
|
||||
error: null,
|
||||
data: null,
|
||||
raw: {}
|
||||
};
|
||||
const errors = await validateChart(yamlChart);
|
||||
|
||||
if (errors.constraints) {
|
||||
result.error = errors.constraints;
|
||||
} else {
|
||||
result.data = {...chart, content: yamlChart, raw: chart};
|
||||
}
|
||||
|
||||
selectedChart.value = typeof result.data === "object"
|
||||
? {
|
||||
...result.data,
|
||||
chartOptions: {
|
||||
...result.data?.chartOptions,
|
||||
width: 12
|
||||
}
|
||||
} as any
|
||||
: undefined;
|
||||
chartErrors.value = [result.error].filter(e => e !== null);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
chartErrors,
|
||||
@@ -155,6 +198,7 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
||||
validateChart,
|
||||
chartPreview,
|
||||
export: exportDashboard,
|
||||
loadChart,
|
||||
|
||||
schema,
|
||||
definitions,
|
||||
|
||||
Reference in New Issue
Block a user