mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-19 18:05:41 -05:00
fix: avoid blocking creation of flow when edition is restricted to a namespace (#13694)
This commit is contained in:
committed by
GitHub
parent
67ada7f61b
commit
abcf76f7b4
@@ -20,6 +20,9 @@
|
|||||||
import {useVueTour} from "../../composables/useVueTour";
|
import {useVueTour} from "../../composables/useVueTour";
|
||||||
|
|
||||||
import type {BlueprintType} from "../../stores/blueprints"
|
import type {BlueprintType} from "../../stores/blueprints"
|
||||||
|
import {useAuthStore} from "../../override/stores/auth";
|
||||||
|
import permission from "../../models/permission";
|
||||||
|
import action from "../../models/action";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
@@ -29,13 +32,21 @@
|
|||||||
const blueprintsStore = useBlueprintsStore();
|
const blueprintsStore = useBlueprintsStore();
|
||||||
const coreStore = useCoreStore();
|
const coreStore = useCoreStore();
|
||||||
const flowStore = useFlowStore();
|
const flowStore = useFlowStore();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const setupFlow = async () => {
|
const setupFlow = async () => {
|
||||||
const blueprintId = route.query.blueprintId as string;
|
const blueprintId = route.query.blueprintId as string;
|
||||||
const blueprintSource = route.query.blueprintSource as BlueprintType;
|
const blueprintSource = route.query.blueprintSource as BlueprintType;
|
||||||
|
const implicitDefaultNamespace = authStore.user.getNamespacesForAction(
|
||||||
|
permission.FLOW,
|
||||||
|
action.CREATE,
|
||||||
|
)[0];
|
||||||
let flowYaml = "";
|
let flowYaml = "";
|
||||||
const id = getRandomID();
|
const id = getRandomID();
|
||||||
const selectedNamespace = (route.query.namespace as string) || defaultNamespace() || "company.team";
|
const selectedNamespace = (route.query.namespace as string)
|
||||||
|
?? defaultNamespace()
|
||||||
|
?? implicitDefaultNamespace
|
||||||
|
?? "company.team";
|
||||||
|
|
||||||
if (route.query.copy && flowStore.flow) {
|
if (route.query.copy && flowStore.flow) {
|
||||||
flowYaml = flowStore.flow.source;
|
flowYaml = flowStore.flow.source;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<span ref="rootContainer">
|
<span ref="rootContainer">
|
||||||
<!-- Valid -->
|
<!-- Valid -->
|
||||||
<el-button v-if="!errors && !warnings &&!infos" v-bind="$attrs" :link="link" :size="size" type="default" class="success square" disabled>
|
<el-button v-if="!errors && !warnings && !infos" v-bind="$attrs" :link="link" :size="size" type="default" class="success square" disabled>
|
||||||
<CheckBoldIcon class="text-success" />
|
<CheckBoldIcon class="text-success" />
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
@@ -157,6 +157,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
|
cursor: default;
|
||||||
border-color: var(--ks-border-success);
|
border-color: var(--ks-border-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,19 +5,19 @@
|
|||||||
<ValidationError
|
<ValidationError
|
||||||
class="validation"
|
class="validation"
|
||||||
tooltipPlacement="bottom-start"
|
tooltipPlacement="bottom-start"
|
||||||
:errors="flowErrors"
|
:errors="flowStore.flowErrors"
|
||||||
:warnings="flowWarnings"
|
:warnings="flowWarnings"
|
||||||
:infos="flowInfos"
|
:infos="flowStore.flowInfos"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditorButtons
|
<EditorButtons
|
||||||
:isCreating="flowStore.isCreating"
|
:isCreating="flowStore.isCreating"
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="flowStore.isReadOnly"
|
||||||
:canDelete="true"
|
:canDelete="true"
|
||||||
:isAllowedEdit="isAllowedEdit"
|
:isAllowedEdit="flowStore.isAllowedEdit"
|
||||||
:haveChange="haveChange"
|
:haveChange="haveChange"
|
||||||
:flowHaveTasks="Boolean(flowHaveTasks)"
|
:flowHaveTasks="Boolean(flowStore.flowHaveTasks)"
|
||||||
:errors="flowErrors"
|
:errors="flowStore.flowErrors"
|
||||||
:warnings="flowWarnings"
|
:warnings="flowWarnings"
|
||||||
@save="save"
|
@save="save"
|
||||||
@copy="
|
@copy="
|
||||||
@@ -49,7 +49,6 @@
|
|||||||
import ValidationError from "../flows/ValidationError.vue";
|
import ValidationError from "../flows/ValidationError.vue";
|
||||||
|
|
||||||
import localUtils from "../../utils/utils";
|
import localUtils from "../../utils/utils";
|
||||||
import {useFlowOutdatedErrors} from "./flowOutdatedErrors";
|
|
||||||
import {useFlowStore} from "../../stores/flow";
|
import {useFlowStore} from "../../stores/flow";
|
||||||
import {useToast} from "../../utils/toast";
|
import {useToast} from "../../utils/toast";
|
||||||
|
|
||||||
@@ -73,22 +72,14 @@
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const routeParams = computed(() => route.params)
|
const routeParams = computed(() => route.params)
|
||||||
|
|
||||||
const {translateError, translateErrorWithKey} = useFlowOutdatedErrors();
|
|
||||||
|
|
||||||
// If playground is not defined, enable it by default
|
// If playground is not defined, enable it by default
|
||||||
const isSettingsPlaygroundEnabled = computed(() => localStorage.getItem("editorPlayground") === "false" ? false : true);
|
const isSettingsPlaygroundEnabled = computed(() => localStorage.getItem("editorPlayground") === "false" ? false : true);
|
||||||
|
|
||||||
const isReadOnly = computed(() => flowStore.isReadOnly)
|
|
||||||
const isAllowedEdit = computed(() => flowStore.isAllowedEdit)
|
|
||||||
const flowHaveTasks = computed(() => flowStore.flowHaveTasks)
|
|
||||||
const flowErrors = computed(() => flowStore.flowErrors?.map(translateError));
|
|
||||||
const flowInfos = computed(() => flowStore.flowInfos)
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const flowWarnings = computed(() => {
|
const flowWarnings = computed(() => {
|
||||||
|
|
||||||
const outdatedWarning =
|
const outdatedWarning =
|
||||||
flowStore.flowValidation?.outdated && !flowStore.isCreating
|
flowStore.flowValidation?.outdated && !flowStore.isCreating
|
||||||
? [translateErrorWithKey(flowStore.flowValidation?.constraints ?? "")]
|
? flowStore.flowValidation?.constraints?.split(", ") ?? []
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const deprecationWarnings =
|
const deprecationWarnings =
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import {useI18n} from "vue-i18n";
|
|
||||||
|
|
||||||
export function useFlowOutdatedErrors(){
|
|
||||||
const {t} = useI18n();
|
|
||||||
function translateError(error: string): string {
|
|
||||||
if(error.startsWith(">>>>")){
|
|
||||||
const key = error.substring(4).trim();
|
|
||||||
return translateErrorWithKey(key);
|
|
||||||
} else {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateErrorWithKey(key: string): string {
|
|
||||||
return `${t(key + ".description")} ${t(key + ".details")}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
translateError,
|
|
||||||
translateErrorWithKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,6 +28,10 @@ export class Me {
|
|||||||
hasAnyRole() {
|
hasAnyRole() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNamespacesForAction(_permission: any, _action: any): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = defineStore("auth", {
|
export const useAuthStore = defineStore("auth", {
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export const useFlowStore = defineStore("flow", () => {
|
|||||||
return validateFlow({
|
return validateFlow({
|
||||||
flow: (isCreating.value ? flowYaml.value : yamlWithNextRevision.value) ?? ""
|
flow: (isCreating.value ? flowYaml.value : yamlWithNextRevision.value) ?? ""
|
||||||
})
|
})
|
||||||
.then((value: {constraints?: any}) => {
|
.then((value: {constraints?: string}) => {
|
||||||
if (
|
if (
|
||||||
topologyVisible &&
|
topologyVisible &&
|
||||||
flowHaveTasks.value &&
|
flowHaveTasks.value &&
|
||||||
@@ -566,7 +566,7 @@ function deleteFlowAndDependencies() {
|
|||||||
coreStore.message = {
|
coreStore.message = {
|
||||||
title: "Couldn't expand subflow",
|
title: "Couldn't expand subflow",
|
||||||
message: error.response.data.message,
|
message: error.response.data.message,
|
||||||
variant: "danger"
|
variant: "error"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,19 +644,37 @@ function deleteFlowAndDependencies() {
|
|||||||
function enableFlowByQuery(options: { namespace: string, id: string }) {
|
function enableFlowByQuery(options: { namespace: string, id: string }) {
|
||||||
return axios.post(`${apiUrl()}/flows/enable/by-query`, options, {params: options})
|
return axios.post(`${apiUrl()}/flows/enable/by-query`, options, {params: options})
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteFlowByIds(options: { ids: {id: string, namespace: string}[] }) {
|
function deleteFlowByIds(options: { ids: {id: string, namespace: string}[] }) {
|
||||||
return axios.delete(`${apiUrl()}/flows/delete/by-ids`, {data: options.ids})
|
return axios.delete(`${apiUrl()}/flows/delete/by-ids`, {data: options.ids})
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteFlowByQuery(options: { namespace: string, id: string }) {
|
function deleteFlowByQuery(options: { namespace: string, id: string }) {
|
||||||
return axios.delete(`${apiUrl()}/flows/delete/by-query`, {params: options})
|
return axios.delete(`${apiUrl()}/flows/delete/by-query`, {params: options})
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateFlow(options: { flow: string }) {
|
function validateFlow(options: { flow: string }) {
|
||||||
|
const flowValidationIssues: FlowValidations = {};
|
||||||
|
if(isCreating.value) {
|
||||||
|
const {namespace} = YAML_UTILS.getMetadata(options.flow);
|
||||||
|
if(authStore.user && !authStore.user.isAllowed(
|
||||||
|
permission.FLOW,
|
||||||
|
action.CREATE,
|
||||||
|
namespace,
|
||||||
|
)) {
|
||||||
|
flowValidationIssues.constraints = t("flow creation denied in namespace", {namespace});
|
||||||
|
}
|
||||||
|
}
|
||||||
return axios.post(`${apiUrl()}/flows/validate`, options.flow, {...textYamlHeader, withCredentials: true})
|
return axios.post(`${apiUrl()}/flows/validate`, options.flow, {...textYamlHeader, withCredentials: true})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
flowValidation.value = response.data[0]
|
const constraintsArray = [response?.data[0]?.constraints, flowValidationIssues.constraints].filter(Boolean)
|
||||||
return response.data[0]
|
flowValidation.value = constraintsArray.length === 0 ? {} : {
|
||||||
|
constraints: constraintsArray.join(", ")
|
||||||
|
};
|
||||||
|
return flowValidation.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateTask(options: { task: string, section: string }) {
|
function validateTask(options: { task: string, section: string }) {
|
||||||
return axios.post(`${apiUrl()}/flows/validate/task`, options.task, {...textYamlHeader, withCredentials: true, params: {section: options.section}})
|
return axios.post(`${apiUrl()}/flows/validate/task`, options.task, {...textYamlHeader, withCredentials: true, params: {section: options.section}})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@@ -752,7 +770,8 @@ function deleteFlowAndDependencies() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return authStore.user.isAllowed(
|
return (isCreating.value && authStore.user.hasAnyAction(permission.FLOW, action.UPDATE))
|
||||||
|
|| authStore.user.isAllowed(
|
||||||
permission.FLOW,
|
permission.FLOW,
|
||||||
action.UPDATE,
|
action.UPDATE,
|
||||||
flow.value?.namespace,
|
flow.value?.namespace,
|
||||||
@@ -777,9 +796,10 @@ function deleteFlowAndDependencies() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const flowErrors = computed((): string[] | undefined => {
|
const flowErrors = computed((): string[] | undefined => {
|
||||||
|
const key = baseOutdatedTranslationKey.value;
|
||||||
const flowExistsError =
|
const flowExistsError =
|
||||||
flowValidation.value?.outdated && isCreating.value
|
flowValidation.value?.outdated && isCreating.value
|
||||||
? [`>>>>${baseOutdatedTranslationKey.value}`] // because translating is impossible here
|
? [`${t(key + ".description")} ${t(key + ".details")}`]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const constraintsError =
|
const constraintsError =
|
||||||
@@ -794,8 +814,6 @@ function deleteFlowAndDependencies() {
|
|||||||
const infos = flowValidation.value?.infos ?? [];
|
const infos = flowValidation.value?.infos ?? [];
|
||||||
|
|
||||||
return infos.length === 0 ? undefined : infos;
|
return infos.length === 0 ? undefined : infos;
|
||||||
|
|
||||||
return undefined;
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const flowHaveTasks = computed((): boolean => {
|
const flowHaveTasks = computed((): boolean => {
|
||||||
|
|||||||
@@ -574,6 +574,7 @@
|
|||||||
"can not save": "Can not save",
|
"can not save": "Can not save",
|
||||||
"flow must not be empty": "Flow must not be empty",
|
"flow must not be empty": "Flow must not be empty",
|
||||||
"flow must have id and namespace": "Flow must have an id and a namespace.",
|
"flow must have id and namespace": "Flow must have an id and a namespace.",
|
||||||
|
"flow creation denied in namespace": "You don't have permission to create flows in the namespace `{namespace}`.",
|
||||||
"readonly property": "Read-only property",
|
"readonly property": "Read-only property",
|
||||||
"namespace and id readonly": "The properties `namespace` and `id` cannot be changed — they are now set to their initial values. If you want to rename a flow or change its namespace, you can create a new flow and remove the old one.",
|
"namespace and id readonly": "The properties `namespace` and `id` cannot be changed — they are now set to their initial values. If you want to rename a flow or change its namespace, you can create a new flow and remove the old one.",
|
||||||
"avg": "Average",
|
"avg": "Average",
|
||||||
|
|||||||
Reference in New Issue
Block a user