mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor(editor): Standardize components sections order (no-changelog) (#10540)
This commit is contained in:
@@ -66,6 +66,12 @@ module.exports = {
|
||||
useAttrs: 'attrs',
|
||||
},
|
||||
],
|
||||
'vue/block-order': [
|
||||
'error',
|
||||
{
|
||||
order: ['script', 'template', 'style'],
|
||||
},
|
||||
],
|
||||
|
||||
// TODO: fix these
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import N8nButton from '../N8nButton';
|
||||
import N8nHeading from '../N8nHeading';
|
||||
import N8nText from '../N8nText';
|
||||
import N8nCallout, { type CalloutTheme } from '../N8nCallout';
|
||||
import type { ButtonType } from 'n8n-design-system/types/button';
|
||||
import N8nTooltip from 'n8n-design-system/components/N8nTooltip/Tooltip.vue';
|
||||
|
||||
interface ActionBoxProps {
|
||||
emoji: string;
|
||||
heading: string;
|
||||
buttonText: string;
|
||||
buttonType: ButtonType;
|
||||
buttonDisabled?: boolean;
|
||||
description: string;
|
||||
calloutText?: string;
|
||||
calloutTheme?: CalloutTheme;
|
||||
calloutIcon?: string;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nActionBox' });
|
||||
withDefaults(defineProps<ActionBoxProps>(), {
|
||||
calloutTheme: 'info',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['n8n-action-box', $style.container]" data-test-id="action-box">
|
||||
<div v-if="emoji" :class="$style.emoji">
|
||||
@@ -41,32 +67,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import N8nButton from '../N8nButton';
|
||||
import N8nHeading from '../N8nHeading';
|
||||
import N8nText from '../N8nText';
|
||||
import N8nCallout, { type CalloutTheme } from '../N8nCallout';
|
||||
import type { ButtonType } from 'n8n-design-system/types/button';
|
||||
import N8nTooltip from 'n8n-design-system/components/N8nTooltip/Tooltip.vue';
|
||||
|
||||
interface ActionBoxProps {
|
||||
emoji: string;
|
||||
heading: string;
|
||||
buttonText: string;
|
||||
buttonType: ButtonType;
|
||||
buttonDisabled?: boolean;
|
||||
description: string;
|
||||
calloutText?: string;
|
||||
calloutTheme?: CalloutTheme;
|
||||
calloutIcon?: string;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nActionBox' });
|
||||
withDefaults(defineProps<ActionBoxProps>(), {
|
||||
calloutTheme: 'info',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
border: 2px dashed var(--color-foreground-base);
|
||||
|
||||
@@ -1,57 +1,3 @@
|
||||
<template>
|
||||
<div :class="['action-dropdown-container', $style.actionDropdownContainer]">
|
||||
<ElDropdown
|
||||
ref="elementDropdown"
|
||||
:placement="placement"
|
||||
:trigger="trigger"
|
||||
:popper-class="popperClass"
|
||||
:teleported="teleported"
|
||||
:disabled="disabled"
|
||||
@command="onSelect"
|
||||
@visible-change="onVisibleChange"
|
||||
>
|
||||
<slot v-if="$slots.activator" name="activator" />
|
||||
<n8n-icon-button
|
||||
v-else
|
||||
type="tertiary"
|
||||
text
|
||||
:class="$style.activator"
|
||||
:size="activatorSize"
|
||||
:icon="activatorIcon"
|
||||
@blur="onButtonBlur"
|
||||
/>
|
||||
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu :class="$style.userActionsMenu">
|
||||
<ElDropdownItem
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:command="item.id"
|
||||
:disabled="item.disabled"
|
||||
:divided="item.divided"
|
||||
:class="$style.elementItem"
|
||||
>
|
||||
<div :class="getItemClasses(item)" :data-test-id="`${testIdPrefix}-item-${item.id}`">
|
||||
<span v-if="item.icon" :class="$style.icon">
|
||||
<N8nIcon :icon="item.icon" :size="iconSize" />
|
||||
</span>
|
||||
<span :class="$style.label">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<N8nKeyboardShortcut
|
||||
v-if="item.shortcut"
|
||||
v-bind="item.shortcut"
|
||||
:class="$style.shortcut"
|
||||
>
|
||||
</N8nKeyboardShortcut>
|
||||
</div>
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// This component is visually similar to the ActionToggle component
|
||||
// but it offers more options when it comes to dropdown items styling
|
||||
@@ -129,6 +75,60 @@ const close = () => elementDropdown.value?.handleClose();
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['action-dropdown-container', $style.actionDropdownContainer]">
|
||||
<ElDropdown
|
||||
ref="elementDropdown"
|
||||
:placement="placement"
|
||||
:trigger="trigger"
|
||||
:popper-class="popperClass"
|
||||
:teleported="teleported"
|
||||
:disabled="disabled"
|
||||
@command="onSelect"
|
||||
@visible-change="onVisibleChange"
|
||||
>
|
||||
<slot v-if="$slots.activator" name="activator" />
|
||||
<n8n-icon-button
|
||||
v-else
|
||||
type="tertiary"
|
||||
text
|
||||
:class="$style.activator"
|
||||
:size="activatorSize"
|
||||
:icon="activatorIcon"
|
||||
@blur="onButtonBlur"
|
||||
/>
|
||||
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu :class="$style.userActionsMenu">
|
||||
<ElDropdownItem
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:command="item.id"
|
||||
:disabled="item.disabled"
|
||||
:divided="item.divided"
|
||||
:class="$style.elementItem"
|
||||
>
|
||||
<div :class="getItemClasses(item)" :data-test-id="`${testIdPrefix}-item-${item.id}`">
|
||||
<span v-if="item.icon" :class="$style.icon">
|
||||
<N8nIcon :icon="item.icon" :size="iconSize" />
|
||||
</span>
|
||||
<span :class="$style.label">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<N8nKeyboardShortcut
|
||||
v-if="item.shortcut"
|
||||
v-bind="item.shortcut"
|
||||
:class="$style.shortcut"
|
||||
>
|
||||
</N8nKeyboardShortcut>
|
||||
</div>
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
:global(.el-dropdown__list) {
|
||||
.userActionsMenu {
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, type Placement } from 'element-plus';
|
||||
import type { UserAction } from 'n8n-design-system/types';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import type { IconOrientation, IconSize } from 'n8n-design-system/types/icon';
|
||||
|
||||
const SIZE = ['mini', 'small', 'medium'] as const;
|
||||
const THEME = ['default', 'dark'] as const;
|
||||
|
||||
interface ActionToggleProps {
|
||||
actions?: UserAction[];
|
||||
placement?: Placement;
|
||||
size?: (typeof SIZE)[number];
|
||||
iconSize?: IconSize;
|
||||
theme?: (typeof THEME)[number];
|
||||
iconOrientation?: IconOrientation;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nActionToggle' });
|
||||
withDefaults(defineProps<ActionToggleProps>(), {
|
||||
actions: () => [],
|
||||
placement: 'bottom',
|
||||
size: 'medium',
|
||||
theme: 'default',
|
||||
iconOrientation: 'vertical',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
action: [value: string];
|
||||
'visible-change': [value: boolean];
|
||||
}>();
|
||||
const onCommand = (value: string) => emit('action', value);
|
||||
const onVisibleChange = (value: boolean) => emit('visible-change', value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="$style.container" data-test-id="action-toggle" @click.stop.prevent>
|
||||
<ElDropdown
|
||||
@@ -41,41 +76,6 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, type Placement } from 'element-plus';
|
||||
import type { UserAction } from 'n8n-design-system/types';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import type { IconOrientation, IconSize } from 'n8n-design-system/types/icon';
|
||||
|
||||
const SIZE = ['mini', 'small', 'medium'] as const;
|
||||
const THEME = ['default', 'dark'] as const;
|
||||
|
||||
interface ActionToggleProps {
|
||||
actions?: UserAction[];
|
||||
placement?: Placement;
|
||||
size?: (typeof SIZE)[number];
|
||||
iconSize?: IconSize;
|
||||
theme?: (typeof THEME)[number];
|
||||
iconOrientation?: IconOrientation;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nActionToggle' });
|
||||
withDefaults(defineProps<ActionToggleProps>(), {
|
||||
actions: () => [],
|
||||
placement: 'bottom',
|
||||
size: 'medium',
|
||||
theme: 'default',
|
||||
iconOrientation: 'vertical',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
action: [value: string];
|
||||
'visible-change': [value: boolean];
|
||||
}>();
|
||||
const onCommand = (value: string) => emit('action', value);
|
||||
const onVisibleChange = (value: boolean) => emit('visible-change', value);
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container > * {
|
||||
line-height: 1;
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
<template>
|
||||
<div :class="alertBoxClassNames" role="alert">
|
||||
<div :class="$style.content">
|
||||
<span v-if="showIcon || $slots.icon" :class="$style.icon">
|
||||
<N8nIcon v-if="showIcon" :icon="icon" />
|
||||
<slot v-else-if="$slots.icon" name="icon" />
|
||||
</span>
|
||||
<div :class="$style.text">
|
||||
<div v-if="$slots.title || title" :class="$style.title">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</div>
|
||||
<div
|
||||
v-if="$slots.default || description"
|
||||
:class="{ [$style.description]: true, [$style.hasTitle]: $slots.title || title }"
|
||||
>
|
||||
<slot>{{ description }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.aside" :class="$style.aside">
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
@@ -76,6 +51,31 @@ const alertBoxClassNames = computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="alertBoxClassNames" role="alert">
|
||||
<div :class="$style.content">
|
||||
<span v-if="showIcon || $slots.icon" :class="$style.icon">
|
||||
<N8nIcon v-if="showIcon" :icon="icon" />
|
||||
<slot v-else-if="$slots.icon" name="icon" />
|
||||
</span>
|
||||
<div :class="$style.text">
|
||||
<div v-if="$slots.title || title" :class="$style.title">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</div>
|
||||
<div
|
||||
v-if="$slots.default || description"
|
||||
:class="{ [$style.description]: true, [$style.hasTitle]: $slots.title || title }"
|
||||
>
|
||||
<slot>{{ description }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.aside" :class="$style.aside">
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
@import '../../css/common/var.scss';
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
<template>
|
||||
<span :class="['n8n-avatar', $style.container]" v-bind="$attrs">
|
||||
<Avatar
|
||||
v-if="name"
|
||||
:size="getSize(size)"
|
||||
:name="name"
|
||||
variant="marble"
|
||||
:colors="getColors(colors)"
|
||||
/>
|
||||
<div v-else :class="[$style.empty, $style[size]]"></div>
|
||||
<span v-if="firstName || lastName" :class="[$style.initials, $style[`text-${size}`]]">
|
||||
{{ initials }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import Avatar from 'vue-boring-avatars';
|
||||
@@ -57,6 +41,22 @@ const sizes: { [size: string]: number } = {
|
||||
const getSize = (size: string): number => sizes[size];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="['n8n-avatar', $style.container]" v-bind="$attrs">
|
||||
<Avatar
|
||||
v-if="name"
|
||||
:size="getSize(size)"
|
||||
:name="name"
|
||||
variant="marble"
|
||||
:colors="getColors(colors)"
|
||||
/>
|
||||
<div v-else :class="[$style.empty, $style[size]]"></div>
|
||||
<span v-if="firstName || lastName" :class="[$style.initials, $style[`text-${size}`]]">
|
||||
{{ initials }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
position: relative;
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
<template>
|
||||
<span :class="['n8n-badge', $style[theme]]">
|
||||
<N8nText :size="size" :bold="bold" :compact="true">
|
||||
<slot></slot>
|
||||
</N8nText>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TextSize } from 'n8n-design-system/types/text';
|
||||
import N8nText from '../N8nText';
|
||||
@@ -34,6 +26,14 @@ withDefaults(defineProps<BadgeProps>(), {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="['n8n-badge', $style[theme]]">
|
||||
<N8nText :size="size" :bold="bold" :compact="true">
|
||||
<slot></slot>
|
||||
</N8nText>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
type BlockUiProps = {
|
||||
show: boolean;
|
||||
};
|
||||
|
||||
withDefaults(defineProps<BlockUiProps>(), {
|
||||
show: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div
|
||||
@@ -9,16 +19,6 @@
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
type BlockUiProps = {
|
||||
show: boolean;
|
||||
};
|
||||
|
||||
withDefaults(defineProps<BlockUiProps>(), {
|
||||
show: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.uiBlocker {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
<template>
|
||||
<component
|
||||
:is="element"
|
||||
:class="classes"
|
||||
:disabled="isDisabled"
|
||||
:aria-disabled="ariaDisabled"
|
||||
:aria-busy="ariaBusy"
|
||||
:href="href"
|
||||
aria-live="polite"
|
||||
v-bind="{
|
||||
...attrs,
|
||||
...(props.nativeType ? { type: props.nativeType } : {}),
|
||||
}"
|
||||
>
|
||||
<span v-if="loading || icon" :class="$style.icon">
|
||||
<N8nSpinner v-if="loading" :size="iconSize" />
|
||||
<N8nIcon v-else-if="icon" :icon="icon" :size="iconSize" />
|
||||
</span>
|
||||
<span v-if="label || $slots.default">
|
||||
<slot>{{ label }}</slot>
|
||||
</span>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCssModule, computed, useAttrs, watchEffect } from 'vue';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
@@ -75,6 +51,30 @@ const classes = computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="element"
|
||||
:class="classes"
|
||||
:disabled="isDisabled"
|
||||
:aria-disabled="ariaDisabled"
|
||||
:aria-busy="ariaBusy"
|
||||
:href="href"
|
||||
aria-live="polite"
|
||||
v-bind="{
|
||||
...attrs,
|
||||
...(props.nativeType ? { type: props.nativeType } : {}),
|
||||
}"
|
||||
>
|
||||
<span v-if="loading || icon" :class="$style.icon">
|
||||
<N8nSpinner v-if="loading" :size="iconSize" />
|
||||
<N8nIcon v-else-if="icon" :icon="icon" :size="iconSize" />
|
||||
</span>
|
||||
<span v-if="label || $slots.default">
|
||||
<slot>{{ label }}</slot>
|
||||
</span>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './Button';
|
||||
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<div :class="classes" role="alert">
|
||||
<div :class="$style.messageSection">
|
||||
<div v-if="!iconless" :class="$style.icon">
|
||||
<N8nIcon :icon="getIcon" :size="getIconSize" />
|
||||
</div>
|
||||
<N8nText size="small">
|
||||
<slot />
|
||||
</N8nText>
|
||||
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
<slot name="trailingContent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
import N8nText from '../N8nText';
|
||||
@@ -70,6 +53,23 @@ const getIconSize = computed<IconSize>(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes" role="alert">
|
||||
<div :class="$style.messageSection">
|
||||
<div v-if="!iconless" :class="$style.icon">
|
||||
<N8nIcon :icon="getIcon" :size="getIconSize" />
|
||||
</div>
|
||||
<N8nText size="small">
|
||||
<slot />
|
||||
</N8nText>
|
||||
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
<slot name="trailingContent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.callout {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
|
||||
interface CardProps {
|
||||
hoverable?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nCard' });
|
||||
const props = withDefaults(defineProps<CardProps>(), {
|
||||
hoverable: false,
|
||||
});
|
||||
|
||||
const $style = useCssModule();
|
||||
const classes = computed(() => ({
|
||||
card: true,
|
||||
[$style.card]: true,
|
||||
[$style.hoverable]: props.hoverable,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes" v-bind="$attrs">
|
||||
<div v-if="$slots.prepend" :class="$style.icon">
|
||||
@@ -20,26 +40,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
|
||||
interface CardProps {
|
||||
hoverable?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nCard' });
|
||||
const props = withDefaults(defineProps<CardProps>(), {
|
||||
hoverable: false,
|
||||
});
|
||||
|
||||
const $style = useCssModule();
|
||||
const classes = computed(() => ({
|
||||
card: true,
|
||||
[$style.card]: true,
|
||||
[$style.hoverable]: props.hoverable,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.card {
|
||||
border-radius: var(--border-radius-large);
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
<template>
|
||||
<ElCheckbox
|
||||
v-bind="$props"
|
||||
ref="checkbox"
|
||||
:class="['n8n-checkbox', $style.n8nCheckbox]"
|
||||
:disabled="disabled"
|
||||
:indeterminate="indeterminate"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
>
|
||||
<slot></slot>
|
||||
<N8nInputLabel
|
||||
v-if="label"
|
||||
:label="label"
|
||||
:tooltip-text="tooltipText"
|
||||
:bold="false"
|
||||
:size="labelSize"
|
||||
@click.prevent="onLabelClick"
|
||||
/>
|
||||
</ElCheckbox>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { ElCheckbox } from 'element-plus';
|
||||
@@ -58,6 +36,28 @@ const onLabelClick = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElCheckbox
|
||||
v-bind="$props"
|
||||
ref="checkbox"
|
||||
:class="['n8n-checkbox', $style.n8nCheckbox]"
|
||||
:disabled="disabled"
|
||||
:indeterminate="indeterminate"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
>
|
||||
<slot></slot>
|
||||
<N8nInputLabel
|
||||
v-if="label"
|
||||
:label="label"
|
||||
:tooltip-text="tooltipText"
|
||||
:bold="false"
|
||||
:size="labelSize"
|
||||
@click.prevent="onLabelClick"
|
||||
/>
|
||||
</ElCheckbox>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.n8nCheckbox {
|
||||
display: flex !important;
|
||||
|
||||
@@ -1,26 +1,3 @@
|
||||
<template>
|
||||
<div class="progress-circle">
|
||||
<svg class="progress-ring" :width="diameter" :height="diameter">
|
||||
<circle
|
||||
:class="$style.progressRingCircle"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke="#DCDFE6"
|
||||
fill="transparent"
|
||||
:r="radius"
|
||||
v-bind="{ cx, cy }"
|
||||
/>
|
||||
<circle
|
||||
:class="$style.progressRingCircle"
|
||||
stroke="#5C4EC2"
|
||||
:stroke-width="strokeWidth"
|
||||
fill="transparent"
|
||||
:r="radius"
|
||||
v-bind="{ cx, cy, style }"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
const props = withDefaults(
|
||||
@@ -50,6 +27,29 @@ const style = computed(() => ({
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="progress-circle">
|
||||
<svg class="progress-ring" :width="diameter" :height="diameter">
|
||||
<circle
|
||||
:class="$style.progressRingCircle"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke="#DCDFE6"
|
||||
fill="transparent"
|
||||
:r="radius"
|
||||
v-bind="{ cx, cy }"
|
||||
/>
|
||||
<circle
|
||||
:class="$style.progressRingCircle"
|
||||
stroke="#5C4EC2"
|
||||
:stroke-width="strokeWidth"
|
||||
fill="transparent"
|
||||
:r="radius"
|
||||
v-bind="{ cx, cy, style }"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.progressRingCircle {
|
||||
transition: stroke-dashoffset 0.35s linear;
|
||||
|
||||
@@ -1,65 +1,3 @@
|
||||
<template>
|
||||
<div :class="classes" v-bind="$attrs">
|
||||
<table :class="$style.datatable">
|
||||
<thead :class="$style.datatableHeader">
|
||||
<tr>
|
||||
<th
|
||||
v-for="column in columns"
|
||||
:key="column.id"
|
||||
:class="column.classes"
|
||||
:style="getThStyle(column)"
|
||||
>
|
||||
{{ column.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="row in visibleRows">
|
||||
<slot name="row" :columns="columns" :row="row" :get-td-value="getTdValue">
|
||||
<tr :key="row.id">
|
||||
<td v-for="column in columns" :key="column.id" :class="column.classes">
|
||||
<component :is="column.render" v-if="column.render" :row="row" :column="column" />
|
||||
<span v-else>{{ getTdValue(row, column) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</slot>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div :class="$style.pagination">
|
||||
<N8nPagination
|
||||
v-if="totalPages > 1"
|
||||
background
|
||||
:pager-count="5"
|
||||
:page-size="rowsPerPage"
|
||||
layout="prev, pager, next"
|
||||
:total="totalRows"
|
||||
:current-page="currentPage"
|
||||
@update:current-page="onUpdateCurrentPage"
|
||||
/>
|
||||
|
||||
<div :class="$style.pageSizeSelector">
|
||||
<N8nSelect
|
||||
size="mini"
|
||||
:model-value="rowsPerPage"
|
||||
teleported
|
||||
@update:model-value="onRowsPerPageChange"
|
||||
>
|
||||
<template #prepend>{{ t('datatable.pageSize') }}</template>
|
||||
<N8nOption
|
||||
v-for="size in rowsPerPageOptions"
|
||||
:key="size"
|
||||
:label="`${size}`"
|
||||
:value="size"
|
||||
/>
|
||||
<N8nOption :label="`All`" value="*"> </N8nOption>
|
||||
</N8nSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useCssModule } from 'vue';
|
||||
import N8nSelect from '../N8nSelect';
|
||||
@@ -138,6 +76,68 @@ function getThStyle(column: DatatableColumn) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes" v-bind="$attrs">
|
||||
<table :class="$style.datatable">
|
||||
<thead :class="$style.datatableHeader">
|
||||
<tr>
|
||||
<th
|
||||
v-for="column in columns"
|
||||
:key="column.id"
|
||||
:class="column.classes"
|
||||
:style="getThStyle(column)"
|
||||
>
|
||||
{{ column.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="row in visibleRows">
|
||||
<slot name="row" :columns="columns" :row="row" :get-td-value="getTdValue">
|
||||
<tr :key="row.id">
|
||||
<td v-for="column in columns" :key="column.id" :class="column.classes">
|
||||
<component :is="column.render" v-if="column.render" :row="row" :column="column" />
|
||||
<span v-else>{{ getTdValue(row, column) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</slot>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div :class="$style.pagination">
|
||||
<N8nPagination
|
||||
v-if="totalPages > 1"
|
||||
background
|
||||
:pager-count="5"
|
||||
:page-size="rowsPerPage"
|
||||
layout="prev, pager, next"
|
||||
:total="totalRows"
|
||||
:current-page="currentPage"
|
||||
@update:current-page="onUpdateCurrentPage"
|
||||
/>
|
||||
|
||||
<div :class="$style.pageSizeSelector">
|
||||
<N8nSelect
|
||||
size="mini"
|
||||
:model-value="rowsPerPage"
|
||||
teleported
|
||||
@update:model-value="onRowsPerPageChange"
|
||||
>
|
||||
<template #prepend>{{ t('datatable.pageSize') }}</template>
|
||||
<N8nOption
|
||||
v-for="size in rowsPerPageOptions"
|
||||
:key="size"
|
||||
:label="`${size}`"
|
||||
:value="size"
|
||||
/>
|
||||
<N8nOption :label="`All`" value="*"> </N8nOption>
|
||||
</N8nSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.datatableWrapper {
|
||||
display: block;
|
||||
|
||||
@@ -1,43 +1,3 @@
|
||||
<template>
|
||||
<div :class="['n8n-form-box', $style.container]">
|
||||
<div v-if="title" :class="$style.heading">
|
||||
<N8nHeading size="xlarge">
|
||||
{{ title }}
|
||||
</N8nHeading>
|
||||
</div>
|
||||
<div :class="$style.inputsContainer">
|
||||
<N8nFormInputs
|
||||
:inputs="inputs"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
@update="onUpdateModelValue"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="secondaryButtonText || buttonText" :class="$style.buttonsContainer">
|
||||
<span v-if="secondaryButtonText" :class="$style.secondaryButtonContainer">
|
||||
<N8nLink size="medium" theme="text" @click="onSecondaryButtonClick">
|
||||
{{ secondaryButtonText }}
|
||||
</N8nLink>
|
||||
</span>
|
||||
<N8nButton
|
||||
v-if="buttonText"
|
||||
:label="buttonText"
|
||||
:loading="buttonLoading"
|
||||
data-test-id="form-submit-button"
|
||||
size="large"
|
||||
@click="onButtonClick"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.actionContainer">
|
||||
<N8nLink v-if="redirectText && redirectLink" :to="redirectLink">
|
||||
{{ redirectText }}
|
||||
</N8nLink>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import N8nFormInputs from '../N8nFormInputs';
|
||||
import N8nHeading from '../N8nHeading';
|
||||
@@ -80,6 +40,46 @@ const onButtonClick = () => formBus.emit('submit');
|
||||
const onSecondaryButtonClick = (event: Event) => emit('secondaryClick', event);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['n8n-form-box', $style.container]">
|
||||
<div v-if="title" :class="$style.heading">
|
||||
<N8nHeading size="xlarge">
|
||||
{{ title }}
|
||||
</N8nHeading>
|
||||
</div>
|
||||
<div :class="$style.inputsContainer">
|
||||
<N8nFormInputs
|
||||
:inputs="inputs"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
@update="onUpdateModelValue"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="secondaryButtonText || buttonText" :class="$style.buttonsContainer">
|
||||
<span v-if="secondaryButtonText" :class="$style.secondaryButtonContainer">
|
||||
<N8nLink size="medium" theme="text" @click="onSecondaryButtonClick">
|
||||
{{ secondaryButtonText }}
|
||||
</N8nLink>
|
||||
</span>
|
||||
<N8nButton
|
||||
v-if="buttonText"
|
||||
:label="buttonText"
|
||||
:loading="buttonLoading"
|
||||
data-test-id="form-submit-button"
|
||||
size="large"
|
||||
@click="onButtonClick"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.actionContainer">
|
||||
<N8nLink v-if="redirectText && redirectLink" :to="redirectLink">
|
||||
{{ redirectText }}
|
||||
</N8nLink>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.heading {
|
||||
display: flex;
|
||||
|
||||
@@ -1,96 +1,3 @@
|
||||
<template>
|
||||
<N8nCheckbox
|
||||
v-if="type === 'checkbox'"
|
||||
ref="inputRef"
|
||||
:label="label"
|
||||
:disabled="disabled"
|
||||
:label-size="labelSize as CheckboxLabelSizePropType"
|
||||
:model-value="modelValue as CheckboxModelValuePropType"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@focus="onFocus"
|
||||
/>
|
||||
<N8nInputLabel
|
||||
v-else-if="type === 'toggle'"
|
||||
:input-name="name"
|
||||
:label="label"
|
||||
:tooltip-text="tooltipText"
|
||||
:required="required && showRequiredAsterisk"
|
||||
>
|
||||
<template #content>
|
||||
{{ tooltipText }}
|
||||
</template>
|
||||
<ElSwitch
|
||||
:model-value="modelValue as SwitchModelValuePropType"
|
||||
:active-color="activeColor"
|
||||
:inactive-color="inactiveColor"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
></ElSwitch>
|
||||
</N8nInputLabel>
|
||||
<N8nInputLabel
|
||||
v-else
|
||||
:input-name="name"
|
||||
:label="label"
|
||||
:tooltip-text="tooltipText"
|
||||
:required="required && showRequiredAsterisk"
|
||||
>
|
||||
<div :class="showErrors ? $style.errorInput : ''" @keydown.stop @keydown.enter="onEnter">
|
||||
<slot v-if="hasDefaultSlot" />
|
||||
<N8nSelect
|
||||
v-else-if="type === 'select' || type === 'multi-select'"
|
||||
ref="inputRef"
|
||||
:class="{ [$style.multiSelectSmallTags]: tagSize === 'small' }"
|
||||
:model-value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:multiple="type === 'multi-select'"
|
||||
:disabled="disabled"
|
||||
:name="name"
|
||||
:teleported="teleported"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<N8nOption
|
||||
v-for="option in options || []"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:label="option.label"
|
||||
:disabled="!!option.disabled"
|
||||
size="small"
|
||||
/>
|
||||
</N8nSelect>
|
||||
<N8nInput
|
||||
v-else
|
||||
ref="inputRef"
|
||||
:name="name"
|
||||
:type="type as InputTypePropType"
|
||||
:placeholder="placeholder"
|
||||
:model-value="modelValue as InputModelValuePropType"
|
||||
:maxlength="maxlength"
|
||||
:autocomplete="autocomplete"
|
||||
:disabled="disabled"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showErrors" :class="$style.errors">
|
||||
<span v-text="validationError" />
|
||||
<n8n-link
|
||||
v-if="documentationUrl && documentationText"
|
||||
:to="documentationUrl"
|
||||
:new-window="true"
|
||||
size="small"
|
||||
theme="danger"
|
||||
>
|
||||
{{ documentationText }}
|
||||
</n8n-link>
|
||||
</div>
|
||||
<div v-else-if="infoText" :class="$style.infoText">
|
||||
<span size="small" v-text="infoText" />
|
||||
</div>
|
||||
</N8nInputLabel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, onMounted, ref, watch, useSlots } from 'vue';
|
||||
|
||||
@@ -271,6 +178,99 @@ watch(
|
||||
defineExpose({ inputRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nCheckbox
|
||||
v-if="type === 'checkbox'"
|
||||
ref="inputRef"
|
||||
:label="label"
|
||||
:disabled="disabled"
|
||||
:label-size="labelSize as CheckboxLabelSizePropType"
|
||||
:model-value="modelValue as CheckboxModelValuePropType"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@focus="onFocus"
|
||||
/>
|
||||
<N8nInputLabel
|
||||
v-else-if="type === 'toggle'"
|
||||
:input-name="name"
|
||||
:label="label"
|
||||
:tooltip-text="tooltipText"
|
||||
:required="required && showRequiredAsterisk"
|
||||
>
|
||||
<template #content>
|
||||
{{ tooltipText }}
|
||||
</template>
|
||||
<ElSwitch
|
||||
:model-value="modelValue as SwitchModelValuePropType"
|
||||
:active-color="activeColor"
|
||||
:inactive-color="inactiveColor"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
></ElSwitch>
|
||||
</N8nInputLabel>
|
||||
<N8nInputLabel
|
||||
v-else
|
||||
:input-name="name"
|
||||
:label="label"
|
||||
:tooltip-text="tooltipText"
|
||||
:required="required && showRequiredAsterisk"
|
||||
>
|
||||
<div :class="showErrors ? $style.errorInput : ''" @keydown.stop @keydown.enter="onEnter">
|
||||
<slot v-if="hasDefaultSlot" />
|
||||
<N8nSelect
|
||||
v-else-if="type === 'select' || type === 'multi-select'"
|
||||
ref="inputRef"
|
||||
:class="{ [$style.multiSelectSmallTags]: tagSize === 'small' }"
|
||||
:model-value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:multiple="type === 'multi-select'"
|
||||
:disabled="disabled"
|
||||
:name="name"
|
||||
:teleported="teleported"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<N8nOption
|
||||
v-for="option in options || []"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:label="option.label"
|
||||
:disabled="!!option.disabled"
|
||||
size="small"
|
||||
/>
|
||||
</N8nSelect>
|
||||
<N8nInput
|
||||
v-else
|
||||
ref="inputRef"
|
||||
:name="name"
|
||||
:type="type as InputTypePropType"
|
||||
:placeholder="placeholder"
|
||||
:model-value="modelValue as InputModelValuePropType"
|
||||
:maxlength="maxlength"
|
||||
:autocomplete="autocomplete"
|
||||
:disabled="disabled"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showErrors" :class="$style.errors">
|
||||
<span v-text="validationError" />
|
||||
<n8n-link
|
||||
v-if="documentationUrl && documentationText"
|
||||
:to="documentationUrl"
|
||||
:new-window="true"
|
||||
size="small"
|
||||
theme="danger"
|
||||
>
|
||||
{{ documentationText }}
|
||||
</n8n-link>
|
||||
</div>
|
||||
<div v-else-if="infoText" :class="$style.infoText">
|
||||
<span size="small" v-text="infoText" />
|
||||
</div>
|
||||
</N8nInputLabel>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.infoText {
|
||||
margin-top: var(--spacing-2xs);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<component :is="tag" :class="['n8n-heading', ...classes]" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
|
||||
@@ -50,6 +44,12 @@ const classes = computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="tag" :class="['n8n-heading', ...classes]" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.bold {
|
||||
font-weight: var(--font-weight-bold);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<N8nText :size="size" :color="color" :compact="true" class="n8n-icon" v-bind="$attrs">
|
||||
<FontAwesomeIcon :icon="icon" :spin="spin" :class="$style[size]" />
|
||||
</N8nText>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FontAwesomeIconProps } from '@fortawesome/vue-fontawesome';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
@@ -24,6 +18,12 @@ withDefaults(defineProps<IconProps>(), {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nText :size="size" :color="color" :compact="true" class="n8n-icon" v-bind="$attrs">
|
||||
<FontAwesomeIcon :icon="icon" :spin="spin" :class="$style[size]" />
|
||||
</N8nText>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.xlarge {
|
||||
width: var(--font-size-xl) !important;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
<template>
|
||||
<N8nButton square v-bind="{ ...$attrs, ...$props }" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IconButtonProps } from 'n8n-design-system/types/button';
|
||||
import N8nButton from '../N8nButton';
|
||||
@@ -17,3 +13,7 @@ withDefaults(defineProps<IconButtonProps>(), {
|
||||
active: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nButton square v-bind="{ ...$attrs, ...$props }" />
|
||||
</template>
|
||||
|
||||
@@ -1,43 +1,3 @@
|
||||
<template>
|
||||
<div :class="['accordion', $style.container]">
|
||||
<div :class="{ [$style.header]: true, [$style.expanded]: expanded }" @click="toggle">
|
||||
<N8nIcon
|
||||
v-if="headerIcon"
|
||||
:icon="headerIcon.icon"
|
||||
:color="headerIcon.color"
|
||||
size="small"
|
||||
class="mr-2xs"
|
||||
/>
|
||||
<N8nText :class="$style.headerText" color="text-base" size="small" align="left" bold>{{
|
||||
title
|
||||
}}</N8nText>
|
||||
<N8nIcon :icon="expanded ? 'chevron-up' : 'chevron-down'" bold />
|
||||
</div>
|
||||
<div
|
||||
v-if="expanded"
|
||||
:class="{ [$style.description]: true, [$style.collapsed]: !expanded }"
|
||||
@click="onClick"
|
||||
>
|
||||
<!-- Info accordion can display list of items with icons or just a HTML description -->
|
||||
<div v-if="items.length > 0" :class="$style.accordionItems">
|
||||
<div v-for="item in items" :key="item.id" :class="$style.accordionItem">
|
||||
<n8n-tooltip :disabled="!item.tooltip">
|
||||
<template #content>
|
||||
<div @click="onTooltipClick(item.id, $event)" v-html="item.tooltip"></div>
|
||||
</template>
|
||||
<N8nIcon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs" />
|
||||
</n8n-tooltip>
|
||||
<N8nText size="small" color="text-base">{{ item.label }}</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
<N8nText color="text-base" size="small" align="left">
|
||||
<span v-html="description"></span>
|
||||
</N8nText>
|
||||
<slot name="customContent"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import N8nText from '../N8nText';
|
||||
@@ -90,6 +50,46 @@ const onClick = (e: MouseEvent) => emit('click:body', e);
|
||||
const onTooltipClick = (item: string, event: MouseEvent) => emit('tooltipClick', item, event);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['accordion', $style.container]">
|
||||
<div :class="{ [$style.header]: true, [$style.expanded]: expanded }" @click="toggle">
|
||||
<N8nIcon
|
||||
v-if="headerIcon"
|
||||
:icon="headerIcon.icon"
|
||||
:color="headerIcon.color"
|
||||
size="small"
|
||||
class="mr-2xs"
|
||||
/>
|
||||
<N8nText :class="$style.headerText" color="text-base" size="small" align="left" bold>{{
|
||||
title
|
||||
}}</N8nText>
|
||||
<N8nIcon :icon="expanded ? 'chevron-up' : 'chevron-down'" bold />
|
||||
</div>
|
||||
<div
|
||||
v-if="expanded"
|
||||
:class="{ [$style.description]: true, [$style.collapsed]: !expanded }"
|
||||
@click="onClick"
|
||||
>
|
||||
<!-- Info accordion can display list of items with icons or just a HTML description -->
|
||||
<div v-if="items.length > 0" :class="$style.accordionItems">
|
||||
<div v-for="item in items" :key="item.id" :class="$style.accordionItem">
|
||||
<n8n-tooltip :disabled="!item.tooltip">
|
||||
<template #content>
|
||||
<div @click="onTooltipClick(item.id, $event)" v-html="item.tooltip"></div>
|
||||
</template>
|
||||
<N8nIcon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs" />
|
||||
</n8n-tooltip>
|
||||
<N8nText size="small" color="text-base">{{ item.label }}</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
<N8nText color="text-base" size="small" align="left">
|
||||
<span v-html="description"></span>
|
||||
</N8nText>
|
||||
<slot name="customContent"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
background-color: var(--color-background-base);
|
||||
|
||||
@@ -1,37 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'n8n-info-tip': true,
|
||||
[$style.infoTip]: true,
|
||||
[$style[theme]]: true,
|
||||
[$style[type]]: true,
|
||||
[$style.bold]: bold,
|
||||
}"
|
||||
>
|
||||
<N8nTooltip
|
||||
v-if="type === 'tooltip'"
|
||||
:placement="tooltipPlacement"
|
||||
:popper-class="$style.tooltipPopper"
|
||||
:disabled="type !== 'tooltip'"
|
||||
>
|
||||
<span :class="$style.iconText" :style="{ color: iconData.color }">
|
||||
<N8nIcon :icon="iconData.icon" />
|
||||
</span>
|
||||
<template #content>
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
</N8nTooltip>
|
||||
<span v-else :class="$style.iconText">
|
||||
<N8nIcon :icon="iconData.icon" />
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import type { Placement } from 'element-plus';
|
||||
@@ -92,6 +58,40 @@ const iconData = computed((): { icon: string; color: string } => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'n8n-info-tip': true,
|
||||
[$style.infoTip]: true,
|
||||
[$style[theme]]: true,
|
||||
[$style[type]]: true,
|
||||
[$style.bold]: bold,
|
||||
}"
|
||||
>
|
||||
<N8nTooltip
|
||||
v-if="type === 'tooltip'"
|
||||
:placement="tooltipPlacement"
|
||||
:popper-class="$style.tooltipPopper"
|
||||
:disabled="type !== 'tooltip'"
|
||||
>
|
||||
<span :class="$style.iconText" :style="{ color: iconData.color }">
|
||||
<N8nIcon :icon="iconData.icon" />
|
||||
</span>
|
||||
<template #content>
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
</N8nTooltip>
|
||||
<span v-else :class="$style.iconText">
|
||||
<N8nIcon :icon="iconData.icon" />
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.infoTip {
|
||||
display: flex;
|
||||
|
||||
@@ -1,35 +1,3 @@
|
||||
<template>
|
||||
<ElInput
|
||||
ref="innerInput"
|
||||
:model-value="modelValue"
|
||||
:type="type"
|
||||
:size="resolvedSize"
|
||||
:class="['n8n-input', ...classes]"
|
||||
:autocomplete="autocomplete"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:clearable="clearable"
|
||||
:rows="rows"
|
||||
:title="title"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-if="$slots.prepend" #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
<template v-if="$slots.append" #append>
|
||||
<slot name="append" />
|
||||
</template>
|
||||
<template v-if="$slots.prefix" #prefix>
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<template v-if="$slots.suffix" #suffix>
|
||||
<slot name="suffix" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { ElInput } from 'element-plus';
|
||||
@@ -96,6 +64,38 @@ const select = () => inputElement.value?.select();
|
||||
defineExpose({ focus, blur, select });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElInput
|
||||
ref="innerInput"
|
||||
:model-value="modelValue"
|
||||
:type="type"
|
||||
:size="resolvedSize"
|
||||
:class="['n8n-input', ...classes]"
|
||||
:autocomplete="autocomplete"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:clearable="clearable"
|
||||
:rows="rows"
|
||||
:title="title"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-if="$slots.prepend" #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
<template v-if="$slots.append" #append>
|
||||
<slot name="append" />
|
||||
</template>
|
||||
<template v-if="$slots.prefix" #prefix>
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<template v-if="$slots.suffix" #suffix>
|
||||
<slot name="suffix" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.xlarge {
|
||||
--input-font-size: var(--font-size-m);
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import N8nText from '../N8nText';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import N8nTooltip from '../N8nTooltip';
|
||||
import type { TextColor } from 'n8n-design-system/types/text';
|
||||
|
||||
const SIZE = ['small', 'medium'] as const;
|
||||
|
||||
interface InputLabelProps {
|
||||
compact?: boolean;
|
||||
color?: TextColor;
|
||||
label?: string;
|
||||
tooltipText?: string;
|
||||
inputName?: string;
|
||||
required?: boolean;
|
||||
bold?: boolean;
|
||||
size?: (typeof SIZE)[number];
|
||||
underline?: boolean;
|
||||
showTooltip?: boolean;
|
||||
showOptions?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nInputLabel' });
|
||||
withDefaults(defineProps<InputLabelProps>(), {
|
||||
compact: false,
|
||||
bold: true,
|
||||
size: 'medium',
|
||||
});
|
||||
|
||||
const addTargetBlank = (html: string) =>
|
||||
html && html.includes('href=') ? html.replace(/href=/g, 'target="_blank" href=') : html;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.container" v-bind="$attrs" data-test-id="input-label">
|
||||
<label
|
||||
@@ -45,39 +78,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import N8nText from '../N8nText';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import N8nTooltip from '../N8nTooltip';
|
||||
import type { TextColor } from 'n8n-design-system/types/text';
|
||||
|
||||
const SIZE = ['small', 'medium'] as const;
|
||||
|
||||
interface InputLabelProps {
|
||||
compact?: boolean;
|
||||
color?: TextColor;
|
||||
label?: string;
|
||||
tooltipText?: string;
|
||||
inputName?: string;
|
||||
required?: boolean;
|
||||
bold?: boolean;
|
||||
size?: (typeof SIZE)[number];
|
||||
underline?: boolean;
|
||||
showTooltip?: boolean;
|
||||
showOptions?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nInputLabel' });
|
||||
withDefaults(defineProps<InputLabelProps>(), {
|
||||
compact: false,
|
||||
bold: true,
|
||||
size: 'medium',
|
||||
});
|
||||
|
||||
const addTargetBlank = (html: string) =>
|
||||
html && html.includes('href=') ? html.replace(/href=/g, 'target="_blank" href=') : html;
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
<template>
|
||||
<N8nRoute :to="to" :new-window="newWindow" v-bind="$attrs" class="n8n-link">
|
||||
<span :class="$style[`${underline ? `${theme}-underline` : theme}`]">
|
||||
<N8nText :size="size" :bold="bold">
|
||||
<slot></slot>
|
||||
</N8nText>
|
||||
</span>
|
||||
</N8nRoute>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
import N8nText from '../N8nText';
|
||||
@@ -35,6 +25,16 @@ withDefaults(defineProps<LinkProps>(), {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nRoute :to="to" :new-window="newWindow" v-bind="$attrs" class="n8n-link">
|
||||
<span :class="$style[`${underline ? `${theme}-underline` : theme}`]">
|
||||
<N8nText :size="size" :bold="bold">
|
||||
<slot></slot>
|
||||
</N8nText>
|
||||
</span>
|
||||
</N8nRoute>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
@import '../../utils';
|
||||
@import '../../css/common/var';
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { ElSkeleton, ElSkeletonItem } from 'element-plus';
|
||||
|
||||
const VARIANT = [
|
||||
'custom',
|
||||
'p',
|
||||
'text',
|
||||
'h1',
|
||||
'h3',
|
||||
'text',
|
||||
'caption',
|
||||
'button',
|
||||
'image',
|
||||
'circle',
|
||||
'rect',
|
||||
] as const;
|
||||
|
||||
interface LoadingProps {
|
||||
animated?: boolean;
|
||||
loading?: boolean;
|
||||
rows?: number;
|
||||
shrinkLast?: boolean;
|
||||
variant?: (typeof VARIANT)[number];
|
||||
}
|
||||
|
||||
withDefaults(defineProps<LoadingProps>(), {
|
||||
animated: true,
|
||||
loading: true,
|
||||
rows: 1,
|
||||
shrinkLast: true,
|
||||
variant: 'p',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElSkeleton
|
||||
:loading="loading"
|
||||
@@ -35,40 +69,6 @@
|
||||
</ElSkeleton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElSkeleton, ElSkeletonItem } from 'element-plus';
|
||||
|
||||
const VARIANT = [
|
||||
'custom',
|
||||
'p',
|
||||
'text',
|
||||
'h1',
|
||||
'h3',
|
||||
'text',
|
||||
'caption',
|
||||
'button',
|
||||
'image',
|
||||
'circle',
|
||||
'rect',
|
||||
] as const;
|
||||
|
||||
interface LoadingProps {
|
||||
animated?: boolean;
|
||||
loading?: boolean;
|
||||
rows?: number;
|
||||
shrinkLast?: boolean;
|
||||
variant?: (typeof VARIANT)[number];
|
||||
}
|
||||
|
||||
withDefaults(defineProps<LoadingProps>(), {
|
||||
animated: true,
|
||||
loading: true,
|
||||
rows: 1,
|
||||
shrinkLast: true,
|
||||
variant: 'p',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.h1Last {
|
||||
width: 40%;
|
||||
|
||||
@@ -1,23 +1,3 @@
|
||||
<template>
|
||||
<div class="n8n-markdown">
|
||||
<div
|
||||
v-if="!loading"
|
||||
ref="editor"
|
||||
:class="$style[theme]"
|
||||
@click="onClick"
|
||||
@mousedown="onMouseDown"
|
||||
@change="onChange"
|
||||
v-html="htmlContent"
|
||||
/>
|
||||
<div v-else :class="$style.markdown">
|
||||
<div v-for="(_, index) in loadingBlocks" :key="index">
|
||||
<N8nLoading :loading="loading" :rows="loadingRows" animated variant="p" />
|
||||
<div :class="$style.spacer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Options as MarkdownOptions } from 'markdown-it';
|
||||
@@ -213,6 +193,26 @@ const onCheckboxChange = (index: number) => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="n8n-markdown">
|
||||
<div
|
||||
v-if="!loading"
|
||||
ref="editor"
|
||||
:class="$style[theme]"
|
||||
@click="onClick"
|
||||
@mousedown="onMouseDown"
|
||||
@change="onChange"
|
||||
v-html="htmlContent"
|
||||
/>
|
||||
<div v-else :class="$style.markdown">
|
||||
<div v-for="(_, index) in loadingBlocks" :key="index">
|
||||
<N8nLoading :loading="loading" :rows="loadingRows" animated variant="p" />
|
||||
<div :class="$style.spacer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.markdown {
|
||||
color: var(--color-text-base);
|
||||
|
||||
@@ -1,58 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
['menu-container']: true,
|
||||
[$style.container]: true,
|
||||
[$style.menuCollapsed]: collapsed,
|
||||
[$style.transparentBackground]: transparentBackground,
|
||||
}"
|
||||
>
|
||||
<div v-if="$slots.header" :class="$style.menuHeader">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div :class="$style.menuContent">
|
||||
<div :class="{ [$style.upperContent]: true, ['pt-xs']: $slots.menuPrefix }">
|
||||
<div v-if="$slots.menuPrefix" :class="$style.menuPrefix">
|
||||
<slot name="menuPrefix"></slot>
|
||||
</div>
|
||||
<ElMenu :default-active="defaultActive" :collapse="collapsed">
|
||||
<N8nMenuItem
|
||||
v-for="item in upperMenuItems"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:compact="collapsed"
|
||||
:tooltip-delay="tooltipDelay"
|
||||
:mode="mode"
|
||||
:active-tab="activeTab"
|
||||
:handle-select="onSelect"
|
||||
/>
|
||||
</ElMenu>
|
||||
</div>
|
||||
<div :class="[$style.lowerContent, 'pb-2xs']">
|
||||
<slot name="beforeLowerMenu"></slot>
|
||||
<ElMenu :default-active="defaultActive" :collapse="collapsed">
|
||||
<N8nMenuItem
|
||||
v-for="item in lowerMenuItems"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:compact="collapsed"
|
||||
:tooltip-delay="tooltipDelay"
|
||||
:mode="mode"
|
||||
:active-tab="activeTab"
|
||||
:handle-select="onSelect"
|
||||
/>
|
||||
</ElMenu>
|
||||
<div v-if="$slots.menuSuffix" :class="$style.menuSuffix">
|
||||
<slot name="menuSuffix"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.footer" :class="$style.menuFooter">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
@@ -125,6 +70,61 @@ const onSelect = (item: IMenuItem): void => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
['menu-container']: true,
|
||||
[$style.container]: true,
|
||||
[$style.menuCollapsed]: collapsed,
|
||||
[$style.transparentBackground]: transparentBackground,
|
||||
}"
|
||||
>
|
||||
<div v-if="$slots.header" :class="$style.menuHeader">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div :class="$style.menuContent">
|
||||
<div :class="{ [$style.upperContent]: true, ['pt-xs']: $slots.menuPrefix }">
|
||||
<div v-if="$slots.menuPrefix" :class="$style.menuPrefix">
|
||||
<slot name="menuPrefix"></slot>
|
||||
</div>
|
||||
<ElMenu :default-active="defaultActive" :collapse="collapsed">
|
||||
<N8nMenuItem
|
||||
v-for="item in upperMenuItems"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:compact="collapsed"
|
||||
:tooltip-delay="tooltipDelay"
|
||||
:mode="mode"
|
||||
:active-tab="activeTab"
|
||||
:handle-select="onSelect"
|
||||
/>
|
||||
</ElMenu>
|
||||
</div>
|
||||
<div :class="[$style.lowerContent, 'pb-2xs']">
|
||||
<slot name="beforeLowerMenu"></slot>
|
||||
<ElMenu :default-active="defaultActive" :collapse="collapsed">
|
||||
<N8nMenuItem
|
||||
v-for="item in lowerMenuItems"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:compact="collapsed"
|
||||
:tooltip-delay="tooltipDelay"
|
||||
:mode="mode"
|
||||
:active-tab="activeTab"
|
||||
:handle-select="onSelect"
|
||||
/>
|
||||
</ElMenu>
|
||||
<div v-if="$slots.menuSuffix" :class="$style.menuSuffix">
|
||||
<slot name="menuSuffix"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.footer" :class="$style.menuFooter">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
height: 100%;
|
||||
|
||||
@@ -1,3 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ElSubMenu, ElMenuItem } from 'element-plus';
|
||||
import N8nTooltip from '../N8nTooltip';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import ConditionalRouterLink from '../ConditionalRouterLink';
|
||||
import type { IMenuItem } from '../../types';
|
||||
import { doesMenuItemMatchCurrentRoute } from './routerUtil';
|
||||
import { getInitials } from '../../utils/labelUtil';
|
||||
|
||||
interface MenuItemProps {
|
||||
item: IMenuItem;
|
||||
compact?: boolean;
|
||||
tooltipDelay?: number;
|
||||
popperClass?: string;
|
||||
mode?: 'router' | 'tabs';
|
||||
activeTab?: string;
|
||||
handleSelect?: (item: IMenuItem) => void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MenuItemProps>(), {
|
||||
compact: false,
|
||||
tooltipDelay: 300,
|
||||
popperClass: '',
|
||||
mode: 'router',
|
||||
});
|
||||
|
||||
const $style = useCssModule();
|
||||
const $route = useRoute();
|
||||
|
||||
const availableChildren = computed((): IMenuItem[] =>
|
||||
Array.isArray(props.item.children)
|
||||
? props.item.children.filter((child) => child.available !== false)
|
||||
: [],
|
||||
);
|
||||
|
||||
const currentRoute = computed(() => {
|
||||
return $route ?? { name: '', path: '' };
|
||||
});
|
||||
|
||||
const submenuPopperClass = computed((): string => {
|
||||
const popperClass = [$style.submenuPopper, props.popperClass];
|
||||
if (props.compact) {
|
||||
popperClass.push($style.compact);
|
||||
}
|
||||
return popperClass.join(' ');
|
||||
});
|
||||
|
||||
const isActive = (item: IMenuItem): boolean => {
|
||||
if (props.mode === 'router') {
|
||||
return doesMenuItemMatchCurrentRoute(item, currentRoute.value);
|
||||
} else {
|
||||
return item.id === props.activeTab;
|
||||
}
|
||||
};
|
||||
|
||||
const isItemActive = (item: IMenuItem): boolean => {
|
||||
const hasActiveChild =
|
||||
Array.isArray(item.children) && item.children.some((child) => isActive(child));
|
||||
return isActive(item) || hasActiveChild;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['n8n-menu-item', $style.item]">
|
||||
<ElSubMenu
|
||||
@@ -88,70 +152,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ElSubMenu, ElMenuItem } from 'element-plus';
|
||||
import N8nTooltip from '../N8nTooltip';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import ConditionalRouterLink from '../ConditionalRouterLink';
|
||||
import type { IMenuItem } from '../../types';
|
||||
import { doesMenuItemMatchCurrentRoute } from './routerUtil';
|
||||
import { getInitials } from '../../utils/labelUtil';
|
||||
|
||||
interface MenuItemProps {
|
||||
item: IMenuItem;
|
||||
compact?: boolean;
|
||||
tooltipDelay?: number;
|
||||
popperClass?: string;
|
||||
mode?: 'router' | 'tabs';
|
||||
activeTab?: string;
|
||||
handleSelect?: (item: IMenuItem) => void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MenuItemProps>(), {
|
||||
compact: false,
|
||||
tooltipDelay: 300,
|
||||
popperClass: '',
|
||||
mode: 'router',
|
||||
});
|
||||
|
||||
const $style = useCssModule();
|
||||
const $route = useRoute();
|
||||
|
||||
const availableChildren = computed((): IMenuItem[] =>
|
||||
Array.isArray(props.item.children)
|
||||
? props.item.children.filter((child) => child.available !== false)
|
||||
: [],
|
||||
);
|
||||
|
||||
const currentRoute = computed(() => {
|
||||
return $route ?? { name: '', path: '' };
|
||||
});
|
||||
|
||||
const submenuPopperClass = computed((): string => {
|
||||
const popperClass = [$style.submenuPopper, props.popperClass];
|
||||
if (props.compact) {
|
||||
popperClass.push($style.compact);
|
||||
}
|
||||
return popperClass.join(' ');
|
||||
});
|
||||
|
||||
const isActive = (item: IMenuItem): boolean => {
|
||||
if (props.mode === 'router') {
|
||||
return doesMenuItemMatchCurrentRoute(item, currentRoute.value);
|
||||
} else {
|
||||
return item.id === props.activeTab;
|
||||
}
|
||||
};
|
||||
|
||||
const isItemActive = (item: IMenuItem): boolean => {
|
||||
const hasActiveChild =
|
||||
Array.isArray(item.children) && item.children.some((child) => isActive(child));
|
||||
return isActive(item) || hasActiveChild;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
// Element menu-item overrides
|
||||
:global(.el-menu-item),
|
||||
|
||||
@@ -1,40 +1,3 @@
|
||||
<template>
|
||||
<div class="n8n-node-icon" v-bind="$attrs">
|
||||
<div
|
||||
:class="{
|
||||
[$style.nodeIconWrapper]: true,
|
||||
[$style.circle]: circle,
|
||||
[$style.disabled]: disabled,
|
||||
}"
|
||||
:style="iconStyleData"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
<N8nTooltip v-if="showTooltip" :placement="tooltipPosition" :disabled="!showTooltip">
|
||||
<template #content>{{ nodeTypeName }}</template>
|
||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||
<FontAwesomeIcon v-else :icon="`${name}`" :class="$style.iconFa" :style="fontStyleData" />
|
||||
</div>
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</N8nTooltip>
|
||||
<template v-else>
|
||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||
<FontAwesomeIcon v-else :icon="`${name}`" :style="fontStyleData" />
|
||||
<div v-if="badge" :class="$style.badge" :style="badgeStyleData">
|
||||
<n8n-node-icon :type="badge.type" :src="badge.src" :size="badgeSize"></n8n-node-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
@@ -107,6 +70,43 @@ const badgeStyleData = computed((): Record<string, string> => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="n8n-node-icon" v-bind="$attrs">
|
||||
<div
|
||||
:class="{
|
||||
[$style.nodeIconWrapper]: true,
|
||||
[$style.circle]: circle,
|
||||
[$style.disabled]: disabled,
|
||||
}"
|
||||
:style="iconStyleData"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
<N8nTooltip v-if="showTooltip" :placement="tooltipPosition" :disabled="!showTooltip">
|
||||
<template #content>{{ nodeTypeName }}</template>
|
||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||
<FontAwesomeIcon v-else :icon="`${name}`" :class="$style.iconFa" :style="fontStyleData" />
|
||||
</div>
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</N8nTooltip>
|
||||
<template v-else>
|
||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||
<FontAwesomeIcon v-else :icon="`${name}`" :style="fontStyleData" />
|
||||
<div v-if="badge" :class="$style.badge" :style="badgeStyleData">
|
||||
<n8n-node-icon :type="badge.type" :src="badge.src" :size="badgeSize"></n8n-node-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.nodeIconWrapper {
|
||||
width: var(--node-icon-size, 26px);
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<div :id="id" :class="classes" role="alert" @click="onClick">
|
||||
<div class="notice-content">
|
||||
<N8nText size="small" :compact="true">
|
||||
<slot>
|
||||
<span
|
||||
:id="`${id}-content`"
|
||||
:class="showFullContent ? $style['expanded'] : $style['truncated']"
|
||||
role="region"
|
||||
v-html="displayContent"
|
||||
/>
|
||||
</slot>
|
||||
</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useCssModule } from 'vue';
|
||||
import sanitize from 'sanitize-html';
|
||||
@@ -81,6 +64,23 @@ const onClick = (event: MouseEvent) => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :id="id" :class="classes" role="alert" @click="onClick">
|
||||
<div class="notice-content">
|
||||
<N8nText size="small" :compact="true">
|
||||
<slot>
|
||||
<span
|
||||
:id="`${id}-content`"
|
||||
:class="showFullContent ? $style['expanded'] : $style['truncated']"
|
||||
role="region"
|
||||
v-html="displayContent"
|
||||
/>
|
||||
</slot>
|
||||
</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.notice {
|
||||
font-size: var(--font-size-2xs);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'N8nPulse' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['pulse', $style.pulseContainer]">
|
||||
<div :class="$style.pulse">
|
||||
@@ -8,10 +12,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'N8nPulse' });
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
$--light-pulse-color: hsla(
|
||||
var(--color-primary-h),
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
interface RadioButtonProps {
|
||||
label: string;
|
||||
value: string;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
size?: 'small' | 'medium';
|
||||
}
|
||||
|
||||
withDefaults(defineProps<RadioButtonProps>(), {
|
||||
active: false,
|
||||
disabled: false,
|
||||
size: 'medium',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label
|
||||
role="radio"
|
||||
@@ -23,22 +39,6 @@
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface RadioButtonProps {
|
||||
label: string;
|
||||
value: string;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
size?: 'small' | 'medium';
|
||||
}
|
||||
|
||||
withDefaults(defineProps<RadioButtonProps>(), {
|
||||
active: false,
|
||||
disabled: false,
|
||||
size: 'medium',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
display: inline-block;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
role="radiogroup"
|
||||
:class="{ 'n8n-radio-buttons': true, [$style.radioGroup]: true, [$style.disabled]: disabled }"
|
||||
>
|
||||
<RadioButton
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
v-bind="option"
|
||||
:active="modelValue === option.value"
|
||||
:size="size"
|
||||
:disabled="disabled || option.disabled"
|
||||
@click.prevent.stop="onClick(option, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import RadioButton from './RadioButton.vue';
|
||||
|
||||
@@ -53,6 +36,23 @@ const onClick = (
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="radiogroup"
|
||||
:class="{ 'n8n-radio-buttons': true, [$style.radioGroup]: true, [$style.disabled]: disabled }"
|
||||
>
|
||||
<RadioButton
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
v-bind="option"
|
||||
:active="modelValue === option.value"
|
||||
:size="size"
|
||||
:disabled="disabled || option.disabled"
|
||||
@click.prevent.stop="onClick(option, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.radioGroup {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.resize">
|
||||
<div
|
||||
v-for="direction in enabledDirections"
|
||||
:key="direction"
|
||||
:data-dir="direction"
|
||||
:class="{ [$style.resizer]: true, [$style[direction]]: true }"
|
||||
@mousedown="resizerMove"
|
||||
/>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
@@ -180,6 +167,19 @@ const resizerMove = (event: MouseEvent) => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.resize">
|
||||
<div
|
||||
v-for="direction in enabledDirections"
|
||||
:key="direction"
|
||||
:data-dir="direction"
|
||||
:class="{ [$style.resizer]: true, [$style[direction]]: true }"
|
||||
@mousedown="resizerMove"
|
||||
/>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.resize {
|
||||
position: relative;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<N8nResizeWrapper
|
||||
:is-resizing-enabled="!readOnly"
|
||||
:height="height"
|
||||
:width="width"
|
||||
:min-height="minHeight"
|
||||
:min-width="minWidth"
|
||||
:scale="scale"
|
||||
:grid-size="gridSize"
|
||||
@resizeend="onResizeEnd"
|
||||
@resize="onResize"
|
||||
@resizestart="onResizeStart"
|
||||
>
|
||||
<N8nSticky v-bind="stickyBindings" />
|
||||
</N8nResizeWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useAttrs } from 'vue';
|
||||
import N8nResizeWrapper, { type ResizeData } from '../N8nResizeWrapper/ResizeWrapper.vue';
|
||||
@@ -59,3 +42,20 @@ const onResizeEnd = () => {
|
||||
emit('resizeend');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nResizeWrapper
|
||||
:is-resizing-enabled="!readOnly"
|
||||
:height="height"
|
||||
:width="width"
|
||||
:min-height="minHeight"
|
||||
:min-width="minWidth"
|
||||
:scale="scale"
|
||||
:grid-size="gridSize"
|
||||
@resizeend="onResizeEnd"
|
||||
@resize="onResize"
|
||||
@resizestart="onResizeStart"
|
||||
>
|
||||
<N8nSticky v-bind="stickyBindings" />
|
||||
</N8nResizeWrapper>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
<template>
|
||||
<router-link v-if="useRouterLink && to" :to="to" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</router-link>
|
||||
<a
|
||||
v-else
|
||||
:href="to ? `${to}` : undefined"
|
||||
:target="openNewWindow ? '_blank' : '_self'"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot></slot>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { type RouteLocationRaw } from 'vue-router';
|
||||
@@ -39,3 +25,17 @@ const useRouterLink = computed(() => {
|
||||
|
||||
const openNewWindow = computed(() => !useRouterLink.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-link v-if="useRouterLink && to" :to="to" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</router-link>
|
||||
<a
|
||||
v-else
|
||||
:href="to ? `${to}` : undefined"
|
||||
:target="openNewWindow ? '_blank' : '_self'"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot></slot>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<template>
|
||||
<span class="n8n-spinner">
|
||||
<div v-if="type === 'ring'" class="lds-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<N8nIcon v-else icon="spinner" :size="size" spin />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TextSize } from 'n8n-design-system/types/text';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
@@ -28,6 +16,18 @@ withDefaults(defineProps<SpinnerProps>(), {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="n8n-spinner">
|
||||
<div v-if="type === 'ring'" class="lds-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<N8nIcon v-else icon="spinner" :size="size" spin />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.lds-ring {
|
||||
display: inline-block;
|
||||
|
||||
@@ -1,51 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'n8n-sticky': true,
|
||||
[$style.sticky]: true,
|
||||
[$style.clickable]: !isResizing,
|
||||
[$style[`color-${backgroundColor}`]]: true,
|
||||
}"
|
||||
:style="styles"
|
||||
@keydown.prevent
|
||||
>
|
||||
<div v-show="!editMode" :class="$style.wrapper" @dblclick.stop="onDoubleClick">
|
||||
<N8nMarkdown
|
||||
theme="sticky"
|
||||
:content="modelValue"
|
||||
:with-multi-breaks="true"
|
||||
@markdown-click="onMarkdownClick"
|
||||
@update-content="onUpdateModelValue"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="editMode"
|
||||
:class="{ 'full-height': !shouldShowFooter, 'sticky-textarea': true }"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@keydown.esc="onInputBlur"
|
||||
@keydown.stop
|
||||
>
|
||||
<N8nInput
|
||||
ref="input"
|
||||
:model-value="modelValue"
|
||||
:name="inputName"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
@blur="onInputBlur"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@wheel="onInputScroll"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
||||
<N8nText size="xsmall" align="right">
|
||||
<span v-html="t('sticky.markdownHint')"></span>
|
||||
</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import N8nInput from '../N8nInput';
|
||||
@@ -122,6 +74,54 @@ const onInputScroll = (event: WheelEvent) => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'n8n-sticky': true,
|
||||
[$style.sticky]: true,
|
||||
[$style.clickable]: !isResizing,
|
||||
[$style[`color-${backgroundColor}`]]: true,
|
||||
}"
|
||||
:style="styles"
|
||||
@keydown.prevent
|
||||
>
|
||||
<div v-show="!editMode" :class="$style.wrapper" @dblclick.stop="onDoubleClick">
|
||||
<N8nMarkdown
|
||||
theme="sticky"
|
||||
:content="modelValue"
|
||||
:with-multi-breaks="true"
|
||||
@markdown-click="onMarkdownClick"
|
||||
@update-content="onUpdateModelValue"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="editMode"
|
||||
:class="{ 'full-height': !shouldShowFooter, 'sticky-textarea': true }"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@keydown.esc="onInputBlur"
|
||||
@keydown.stop
|
||||
>
|
||||
<N8nInput
|
||||
ref="input"
|
||||
:model-value="modelValue"
|
||||
:name="inputName"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
@blur="onInputBlur"
|
||||
@update:model-value="onUpdateModelValue"
|
||||
@wheel="onInputScroll"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
||||
<N8nText size="xsmall" align="right">
|
||||
<span v-html="t('sticky.markdownHint')"></span>
|
||||
</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.sticky {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,59 +1,3 @@
|
||||
<template>
|
||||
<div :class="['n8n-tabs', $style.container]">
|
||||
<div v-if="scrollPosition > 0" :class="$style.back" @click="scrollLeft">
|
||||
<N8nIcon icon="chevron-left" size="small" />
|
||||
</div>
|
||||
<div v-if="canScrollRight" :class="$style.next" @click="scrollRight">
|
||||
<N8nIcon icon="chevron-right" size="small" />
|
||||
</div>
|
||||
<div ref="tabs" :class="$style.tabs">
|
||||
<div
|
||||
v-for="option in options"
|
||||
:id="option.value"
|
||||
:key="option.value"
|
||||
:class="{ [$style.alignRight]: option.align === 'right' }"
|
||||
>
|
||||
<N8nTooltip :disabled="!option.tooltip" placement="bottom">
|
||||
<template #content>
|
||||
<div @click="handleTooltipClick(option.value, $event)" v-html="option.tooltip" />
|
||||
</template>
|
||||
<a
|
||||
v-if="option.href"
|
||||
target="_blank"
|
||||
:href="option.href"
|
||||
:class="[$style.link, $style.tab]"
|
||||
@click="() => handleTabClick(option.value)"
|
||||
>
|
||||
<div>
|
||||
{{ option.label }}
|
||||
<span :class="$style.external">
|
||||
<N8nIcon icon="external-link-alt" size="xsmall" />
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<router-link
|
||||
v-else-if="option.to"
|
||||
:to="option.to"
|
||||
:class="[$style.tab, { [$style.activeTab]: modelValue === option.value }]"
|
||||
>
|
||||
<N8nIcon v-if="option.icon" :icon="option.icon" size="medium" />
|
||||
<span v-if="option.label">{{ option.label }}</span>
|
||||
</router-link>
|
||||
<div
|
||||
v-else
|
||||
:class="{ [$style.tab]: true, [$style.activeTab]: modelValue === option.value }"
|
||||
:data-test-id="`tab-${option.value}`"
|
||||
@click="() => handleTabClick(option.value)"
|
||||
>
|
||||
<N8nIcon v-if="option.icon" :icon="option.icon" size="small" />
|
||||
<span v-if="option.label">{{ option.label }}</span>
|
||||
</div>
|
||||
</N8nTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
@@ -128,6 +72,62 @@ const scrollLeft = () => scroll(-50);
|
||||
const scrollRight = () => scroll(50);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['n8n-tabs', $style.container]">
|
||||
<div v-if="scrollPosition > 0" :class="$style.back" @click="scrollLeft">
|
||||
<N8nIcon icon="chevron-left" size="small" />
|
||||
</div>
|
||||
<div v-if="canScrollRight" :class="$style.next" @click="scrollRight">
|
||||
<N8nIcon icon="chevron-right" size="small" />
|
||||
</div>
|
||||
<div ref="tabs" :class="$style.tabs">
|
||||
<div
|
||||
v-for="option in options"
|
||||
:id="option.value"
|
||||
:key="option.value"
|
||||
:class="{ [$style.alignRight]: option.align === 'right' }"
|
||||
>
|
||||
<N8nTooltip :disabled="!option.tooltip" placement="bottom">
|
||||
<template #content>
|
||||
<div @click="handleTooltipClick(option.value, $event)" v-html="option.tooltip" />
|
||||
</template>
|
||||
<a
|
||||
v-if="option.href"
|
||||
target="_blank"
|
||||
:href="option.href"
|
||||
:class="[$style.link, $style.tab]"
|
||||
@click="() => handleTabClick(option.value)"
|
||||
>
|
||||
<div>
|
||||
{{ option.label }}
|
||||
<span :class="$style.external">
|
||||
<N8nIcon icon="external-link-alt" size="xsmall" />
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<router-link
|
||||
v-else-if="option.to"
|
||||
:to="option.to"
|
||||
:class="[$style.tab, { [$style.activeTab]: modelValue === option.value }]"
|
||||
>
|
||||
<N8nIcon v-if="option.icon" :icon="option.icon" size="medium" />
|
||||
<span v-if="option.label">{{ option.label }}</span>
|
||||
</router-link>
|
||||
<div
|
||||
v-else
|
||||
:class="{ [$style.tab]: true, [$style.activeTab]: modelValue === option.value }"
|
||||
:data-test-id="`tab-${option.value}`"
|
||||
@click="() => handleTabClick(option.value)"
|
||||
>
|
||||
<N8nIcon v-if="option.icon" :icon="option.icon" size="small" />
|
||||
<span v-if="option.label">{{ option.label }}</span>
|
||||
</div>
|
||||
</N8nTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
position: relative;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<span :class="['n8n-tag', $style.tag]" v-bind="$attrs">
|
||||
{{ text }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface TagProps {
|
||||
text: string;
|
||||
@@ -12,6 +6,12 @@ defineOptions({ name: 'N8nTag' });
|
||||
defineProps<TagProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="['n8n-tag', $style.tag]" v-bind="$attrs">
|
||||
{{ text }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.tag {
|
||||
min-width: max-content;
|
||||
|
||||
@@ -1,23 +1,3 @@
|
||||
<template>
|
||||
<div :class="['n8n-tags', $style.tags]">
|
||||
<N8nTag
|
||||
v-for="tag in visibleTags"
|
||||
:key="tag.id"
|
||||
:text="tag.name"
|
||||
@click="emit('click:tag', tag.id, $event)"
|
||||
/>
|
||||
<N8nLink
|
||||
v-if="truncate && !showAll && hiddenTagsLength > 0"
|
||||
theme="text"
|
||||
underline
|
||||
size="small"
|
||||
@click.stop.prevent="onExpand"
|
||||
>
|
||||
{{ t('tags.showMore', [`${hiddenTagsLength}`]) }}
|
||||
</N8nLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import N8nTag from '../N8nTag';
|
||||
@@ -67,6 +47,26 @@ const onExpand = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['n8n-tags', $style.tags]">
|
||||
<N8nTag
|
||||
v-for="tag in visibleTags"
|
||||
:key="tag.id"
|
||||
:text="tag.name"
|
||||
@click="emit('click:tag', tag.id, $event)"
|
||||
/>
|
||||
<N8nLink
|
||||
v-if="truncate && !showAll && hiddenTagsLength > 0"
|
||||
theme="text"
|
||||
underline
|
||||
size="small"
|
||||
@click.stop.prevent="onExpand"
|
||||
>
|
||||
{{ t('tags.showMore', [`${hiddenTagsLength}`]) }}
|
||||
</N8nLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.tags {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<component :is="tag" :class="['n8n-text', ...classes]" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
import type { TextSize, TextColor, TextAlign } from 'n8n-design-system/types/text';
|
||||
@@ -46,6 +40,12 @@ const classes = computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="tag" :class="['n8n-text', ...classes]" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.bold {
|
||||
font-weight: var(--font-weight-bold);
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
<template>
|
||||
<ElTooltip v-bind="{ ...$props, ...$attrs }" :popper-class="$props.popperClass ?? 'n8n-tooltip'">
|
||||
<slot />
|
||||
<template #content>
|
||||
<slot name="content">
|
||||
<div v-html="content"></div>
|
||||
</slot>
|
||||
<div
|
||||
v-if="buttons.length"
|
||||
:class="$style.buttons"
|
||||
:style="{ justifyContent: justifyButtons }"
|
||||
>
|
||||
<N8nButton
|
||||
v-for="button in buttons"
|
||||
:key="button.attrs.label"
|
||||
v-bind="{ ...button.attrs, ...button.listeners }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</ElTooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
@@ -65,6 +43,28 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElTooltip v-bind="{ ...$props, ...$attrs }" :popper-class="$props.popperClass ?? 'n8n-tooltip'">
|
||||
<slot />
|
||||
<template #content>
|
||||
<slot name="content">
|
||||
<div v-html="content"></div>
|
||||
</slot>
|
||||
<div
|
||||
v-if="buttons.length"
|
||||
:class="$style.buttons"
|
||||
:style="{ justifyContent: justifyButtons }"
|
||||
>
|
||||
<N8nButton
|
||||
v-for="button in buttons"
|
||||
:key="button.attrs.label"
|
||||
v-bind="{ ...button.attrs, ...button.listeners }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</ElTooltip>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.buttons {
|
||||
display: flex;
|
||||
|
||||
@@ -1,31 +1,3 @@
|
||||
<template>
|
||||
<div v-if="isObject(value)" class="n8n-tree">
|
||||
<div v-for="(label, i) in Object.keys(value)" :key="i" :class="classes">
|
||||
<div v-if="isSimple(value[label])" :class="$style.simple">
|
||||
<slot v-if="$slots.label" name="label" :label="label" :path="getPath(label)" />
|
||||
<span v-else>{{ label }}</span>
|
||||
<span>:</span>
|
||||
<slot v-if="$slots.value" name="value" :value="value[label]" />
|
||||
<span v-else>{{ value[label] }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<slot v-if="$slots.label" name="label" :label="label" :path="getPath(label)" />
|
||||
<span v-else>{{ label }}</span>
|
||||
<n8n-tree
|
||||
:path="getPath(label)"
|
||||
:depth="depth + 1"
|
||||
:value="value[label] as Record<string, unknown>"
|
||||
:node-class="nodeClass"
|
||||
>
|
||||
<template v-for="(_, name) in $slots" #[name]="data">
|
||||
<slot :name="name" v-bind="data"></slot>
|
||||
</template>
|
||||
</n8n-tree>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
|
||||
@@ -85,6 +57,34 @@ const getPath = (key: string): Array<string | number> => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isObject(value)" class="n8n-tree">
|
||||
<div v-for="(label, i) in Object.keys(value)" :key="i" :class="classes">
|
||||
<div v-if="isSimple(value[label])" :class="$style.simple">
|
||||
<slot v-if="$slots.label" name="label" :label="label" :path="getPath(label)" />
|
||||
<span v-else>{{ label }}</span>
|
||||
<span>:</span>
|
||||
<slot v-if="$slots.value" name="value" :value="value[label]" />
|
||||
<span v-else>{{ value[label] }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<slot v-if="$slots.label" name="label" :label="label" :path="getPath(label)" />
|
||||
<span v-else>{{ label }}</span>
|
||||
<n8n-tree
|
||||
:path="getPath(label)"
|
||||
:depth="depth + 1"
|
||||
:value="value[label] as Record<string, unknown>"
|
||||
:node-class="nodeClass"
|
||||
>
|
||||
<template v-for="(_, name) in $slots" #[name]="data">
|
||||
<slot :name="name" v-bind="data"></slot>
|
||||
</template>
|
||||
</n8n-tree>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
$--spacing: var(--spacing-s);
|
||||
|
||||
|
||||
@@ -1,30 +1,3 @@
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<div :class="$style.avatarContainer">
|
||||
<N8nAvatar :first-name="firstName" :last-name="lastName" />
|
||||
</div>
|
||||
|
||||
<div v-if="isPendingUser" :class="$style.pendingUser">
|
||||
<N8nText :bold="true">{{ email }}</N8nText>
|
||||
<span :class="$style.pendingBadge"><N8nBadge :bold="true">Pending</N8nBadge></span>
|
||||
</div>
|
||||
<div v-else :class="$style.infoContainer">
|
||||
<div>
|
||||
<N8nText :bold="true" color="text-dark">
|
||||
{{ firstName }} {{ lastName }}
|
||||
{{ isCurrentUser ? t('nds.userInfo.you') : '' }}
|
||||
</N8nText>
|
||||
<span v-if="disabled" :class="$style.pendingBadge">
|
||||
<N8nBadge :bold="true">Disabled</N8nBadge>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<N8nText data-test-id="user-email" size="small" color="text-light">{{ email }}</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useCssModule } from 'vue';
|
||||
import N8nText from '../N8nText';
|
||||
@@ -59,6 +32,33 @@ const classes = computed(
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<div :class="$style.avatarContainer">
|
||||
<N8nAvatar :first-name="firstName" :last-name="lastName" />
|
||||
</div>
|
||||
|
||||
<div v-if="isPendingUser" :class="$style.pendingUser">
|
||||
<N8nText :bold="true">{{ email }}</N8nText>
|
||||
<span :class="$style.pendingBadge"><N8nBadge :bold="true">Pending</N8nBadge></span>
|
||||
</div>
|
||||
<div v-else :class="$style.infoContainer">
|
||||
<div>
|
||||
<N8nText :bold="true" color="text-dark">
|
||||
{{ firstName }} {{ lastName }}
|
||||
{{ isCurrentUser ? t('nds.userInfo.you') : '' }}
|
||||
</N8nText>
|
||||
<span v-if="disabled" :class="$style.pendingBadge">
|
||||
<N8nBadge :bold="true">Disabled</N8nBadge>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<N8nText data-test-id="user-email" size="small" color="text-light">{{ email }}</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -1,35 +1,3 @@
|
||||
<template>
|
||||
<N8nSelect
|
||||
data-test-id="user-select-trigger"
|
||||
v-bind="$attrs"
|
||||
:model-value="modelValue"
|
||||
:filterable="true"
|
||||
:filter-method="setFilter"
|
||||
:placeholder="placeholder || t('nds.userSelect.selectUser')"
|
||||
:default-first-option="true"
|
||||
teleported
|
||||
:popper-class="$style.limitPopperWidth"
|
||||
:no-data-text="t('nds.userSelect.noMatchingUsers')"
|
||||
:size="size"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
>
|
||||
<template v-if="$slots.prefix" #prefix>
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<N8nOption
|
||||
v-for="user in sortedUsers"
|
||||
:key="user.id"
|
||||
:value="user.id"
|
||||
:class="$style.itemContainer"
|
||||
:label="getLabel(user)"
|
||||
:disabled="user.disabled"
|
||||
>
|
||||
<N8nUserInfo v-bind="user" :is-current-user="currentUserId === user.id" />
|
||||
</N8nOption>
|
||||
</N8nSelect>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import N8nUserInfo from '../N8nUserInfo';
|
||||
@@ -112,6 +80,38 @@ const getLabel = (user: IUser) =>
|
||||
!user.fullName ? user.email : `${user.fullName} (${user.email})`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nSelect
|
||||
data-test-id="user-select-trigger"
|
||||
v-bind="$attrs"
|
||||
:model-value="modelValue"
|
||||
:filterable="true"
|
||||
:filter-method="setFilter"
|
||||
:placeholder="placeholder || t('nds.userSelect.selectUser')"
|
||||
:default-first-option="true"
|
||||
teleported
|
||||
:popper-class="$style.limitPopperWidth"
|
||||
:no-data-text="t('nds.userSelect.noMatchingUsers')"
|
||||
:size="size"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
>
|
||||
<template v-if="$slots.prefix" #prefix>
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<N8nOption
|
||||
v-for="user in sortedUsers"
|
||||
:key="user.id"
|
||||
:value="user.id"
|
||||
:class="$style.itemContainer"
|
||||
:label="getLabel(user)"
|
||||
:disabled="user.disabled"
|
||||
>
|
||||
<N8nUserInfo v-bind="user" :is-current-user="currentUserId === user.id" />
|
||||
</N8nOption>
|
||||
</N8nSelect>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.itemContainer {
|
||||
--select-option-padding: var(--spacing-2xs) var(--spacing-s);
|
||||
|
||||
@@ -1,39 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-for="(user, i) in sortedUsers"
|
||||
:key="user.id"
|
||||
:class="i === sortedUsers.length - 1 ? $style.itemContainer : $style.itemWithBorder"
|
||||
:data-test-id="`user-list-item-${user.email}`"
|
||||
>
|
||||
<N8nUserInfo
|
||||
v-bind="user"
|
||||
:is-current-user="currentUserId === user.id"
|
||||
:is-saml-login-enabled="isSamlLoginEnabled"
|
||||
/>
|
||||
<div :class="$style.badgeContainer">
|
||||
<N8nBadge v-if="user.isOwner" theme="tertiary" bold>
|
||||
{{ t('nds.auth.roles.owner') }}
|
||||
</N8nBadge>
|
||||
<slot v-if="!user.isOwner && !readonly" name="actions" :user="user" />
|
||||
<N8nActionToggle
|
||||
v-if="
|
||||
!user.isOwner &&
|
||||
user.signInType !== 'ldap' &&
|
||||
!readonly &&
|
||||
getActions(user).length > 0 &&
|
||||
actions.length > 0
|
||||
"
|
||||
placement="bottom"
|
||||
:actions="getActions(user)"
|
||||
theme="dark"
|
||||
@action="(action: string) => onUserAction(user, action)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import N8nActionToggle from '../N8nActionToggle';
|
||||
@@ -115,6 +79,42 @@ const onUserAction = (user: IUser, action: string) =>
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-for="(user, i) in sortedUsers"
|
||||
:key="user.id"
|
||||
:class="i === sortedUsers.length - 1 ? $style.itemContainer : $style.itemWithBorder"
|
||||
:data-test-id="`user-list-item-${user.email}`"
|
||||
>
|
||||
<N8nUserInfo
|
||||
v-bind="user"
|
||||
:is-current-user="currentUserId === user.id"
|
||||
:is-saml-login-enabled="isSamlLoginEnabled"
|
||||
/>
|
||||
<div :class="$style.badgeContainer">
|
||||
<N8nBadge v-if="user.isOwner" theme="tertiary" bold>
|
||||
{{ t('nds.auth.roles.owner') }}
|
||||
</N8nBadge>
|
||||
<slot v-if="!user.isOwner && !readonly" name="actions" :user="user" />
|
||||
<N8nActionToggle
|
||||
v-if="
|
||||
!user.isOwner &&
|
||||
user.signInType !== 'ldap' &&
|
||||
!readonly &&
|
||||
getActions(user).length > 0 &&
|
||||
actions.length > 0
|
||||
"
|
||||
placement="bottom"
|
||||
:actions="getActions(user)"
|
||||
theme="dark"
|
||||
@action="(action: string) => onUserAction(user, action)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.itemContainer {
|
||||
display: flex;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<table :class="$style.table">
|
||||
<tr>
|
||||
<th :class="$style.row">Name</th>
|
||||
<th :class="$style.row">Value</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="variable in variables"
|
||||
:key="variable"
|
||||
:style="attr ? { [attr]: `var(${variable})` } : {}"
|
||||
>
|
||||
<td>{{ variable }}</td>
|
||||
<td>{{ values[variable] }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
|
||||
@@ -64,6 +47,23 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table :class="$style.table">
|
||||
<tr>
|
||||
<th :class="$style.row">Name</th>
|
||||
<th :class="$style.row">Value</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="variable in variables"
|
||||
:key="variable"
|
||||
:style="attr ? { [attr]: `var(${variable})` } : {}"
|
||||
>
|
||||
<td>{{ variable }}</td>
|
||||
<td>{{ values[variable] }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.table {
|
||||
text-align: center;
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="size in sizes" :key="size" class="spacing-group">
|
||||
<div class="spacing-example" :class="`${property[0]}${side ? side[0] : ''}-${size}`">
|
||||
<div class="spacing-box" />
|
||||
<div class="label">{{ property[0] }}{{ side ? side[0] : '' }}-{{ size }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
@@ -42,6 +31,17 @@ const props = withDefaults(defineProps<SpacingPreviewProps>(), {
|
||||
const sizes = computed(() => [...SIZES, ...(props.property === 'margin' ? ['auto'] : [])]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="size in sizes" :key="size" class="spacing-group">
|
||||
<div class="spacing-example" :class="`${property[0]}${side ? side[0] : ''}-${size}`">
|
||||
<div class="spacing-box" />
|
||||
<div class="label">{{ property[0] }}{{ side ? side[0] : '' }}-{{ size }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
$box-size: 64px;
|
||||
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import { createEventBus } from 'n8n-design-system/utils';
|
||||
import Modal from './Modal.vue';
|
||||
import { ABOUT_MODAL_KEY } from '../constants';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useClipboard } from '@/composables/useClipboard';
|
||||
import { useDebugInfo } from '@/composables/useDebugInfo';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'About',
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ABOUT_MODAL_KEY,
|
||||
modalBus: createEventBus(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useRootStore, useSettingsStore),
|
||||
},
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.modalBus.emit('close');
|
||||
},
|
||||
async copyDebugInfoToClipboard() {
|
||||
useToast().showToast({
|
||||
title: this.$locale.baseText('about.debug.toast.title'),
|
||||
message: this.$locale.baseText('about.debug.toast.message'),
|
||||
type: 'info',
|
||||
duration: 5000,
|
||||
});
|
||||
await useClipboard().copy(useDebugInfo().generateDebugInfo());
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
max-width="540px"
|
||||
@@ -68,49 +111,6 @@
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import { createEventBus } from 'n8n-design-system/utils';
|
||||
import Modal from './Modal.vue';
|
||||
import { ABOUT_MODAL_KEY } from '../constants';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useClipboard } from '@/composables/useClipboard';
|
||||
import { useDebugInfo } from '@/composables/useDebugInfo';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'About',
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ABOUT_MODAL_KEY,
|
||||
modalBus: createEventBus(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useRootStore, useSettingsStore),
|
||||
},
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.modalBus.emit('close');
|
||||
},
|
||||
async copyDebugInfoToClipboard() {
|
||||
useToast().showToast({
|
||||
title: this.$locale.baseText('about.debug.toast.title'),
|
||||
message: this.$locale.baseText('about.debug.toast.message'),
|
||||
type: 'info',
|
||||
duration: 5000,
|
||||
});
|
||||
await useClipboard().copy(useDebugInfo().generateDebugInfo());
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.container > * {
|
||||
margin-bottom: var(--spacing-s);
|
||||
|
||||
@@ -1,39 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="WORKFLOW_ACTIVE_MODAL_KEY"
|
||||
:title="$locale.baseText('activationModal.workflowActivated')"
|
||||
width="460px"
|
||||
>
|
||||
<template #content>
|
||||
<div>
|
||||
<n8n-text>{{ triggerContent }}</n8n-text>
|
||||
</div>
|
||||
<div :class="$style.spaced">
|
||||
<n8n-text>
|
||||
<n8n-text :bold="true">
|
||||
{{ $locale.baseText('activationModal.theseExecutionsWillNotShowUp') }}
|
||||
</n8n-text>
|
||||
{{ $locale.baseText('activationModal.butYouCanSeeThem') }}
|
||||
<a @click="showExecutionsList">
|
||||
{{ $locale.baseText('activationModal.executionList') }}
|
||||
</a>
|
||||
{{ $locale.baseText('activationModal.ifYouChooseTo') }}
|
||||
<a @click="showSettings">{{ $locale.baseText('activationModal.saveExecutions') }}</a>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer="{ close }">
|
||||
<div :class="$style.footer">
|
||||
<el-checkbox :model-value="checked" @update:model-value="handleCheckboxChange">{{
|
||||
$locale.baseText('generic.dontShowAgain')
|
||||
}}</el-checkbox>
|
||||
<n8n-button :label="$locale.baseText('activationModal.gotIt')" @click="close" />
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
@@ -134,6 +98,42 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:name="WORKFLOW_ACTIVE_MODAL_KEY"
|
||||
:title="$locale.baseText('activationModal.workflowActivated')"
|
||||
width="460px"
|
||||
>
|
||||
<template #content>
|
||||
<div>
|
||||
<n8n-text>{{ triggerContent }}</n8n-text>
|
||||
</div>
|
||||
<div :class="$style.spaced">
|
||||
<n8n-text>
|
||||
<n8n-text :bold="true">
|
||||
{{ $locale.baseText('activationModal.theseExecutionsWillNotShowUp') }}
|
||||
</n8n-text>
|
||||
{{ $locale.baseText('activationModal.butYouCanSeeThem') }}
|
||||
<a @click="showExecutionsList">
|
||||
{{ $locale.baseText('activationModal.executionList') }}
|
||||
</a>
|
||||
{{ $locale.baseText('activationModal.ifYouChooseTo') }}
|
||||
<a @click="showSettings">{{ $locale.baseText('activationModal.saveExecutions') }}</a>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer="{ close }">
|
||||
<div :class="$style.footer">
|
||||
<el-checkbox :model-value="checked" @update:model-value="handleCheckboxChange">{{
|
||||
$locale.baseText('generic.dontShowAgain')
|
||||
}}</el-checkbox>
|
||||
<n8n-button :label="$locale.baseText('activationModal.gotIt')" @click="close" />
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.spaced {
|
||||
margin-top: var(--spacing-2xs);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ['text', 'type'],
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-tag
|
||||
v-if="type === 'danger'"
|
||||
@@ -18,12 +24,6 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ['text', 'type'],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
|
||||
@@ -1,40 +1,3 @@
|
||||
<template>
|
||||
<el-tag :type="theme" :disable-transitions="true" :class="$style.container">
|
||||
<font-awesome-icon
|
||||
:icon="theme === 'success' ? 'check-circle' : 'exclamation-triangle'"
|
||||
:class="theme === 'success' ? $style.icon : $style.dangerIcon"
|
||||
/>
|
||||
<div :class="$style.banner">
|
||||
<div :class="$style.content">
|
||||
<div>
|
||||
<span :class="theme === 'success' ? $style.message : $style.dangerMessage">
|
||||
{{ message }}
|
||||
</span>
|
||||
<n8n-link v-if="details && !expanded" :bold="true" size="small" @click="expand">
|
||||
<span :class="$style.moreDetails">More details</span>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot v-if="$slots.button" name="button" />
|
||||
<n8n-button
|
||||
v-else-if="buttonLabel"
|
||||
:label="buttonLoading && buttonLoadingLabel ? buttonLoadingLabel : buttonLabel"
|
||||
:title="buttonTitle"
|
||||
:type="theme"
|
||||
:loading="buttonLoading"
|
||||
size="small"
|
||||
outline
|
||||
@click.stop="onClick"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="expanded" :class="$style.details">
|
||||
{{ details }}
|
||||
</div>
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
@@ -72,6 +35,43 @@ const onClick = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-tag :type="theme" :disable-transitions="true" :class="$style.container">
|
||||
<font-awesome-icon
|
||||
:icon="theme === 'success' ? 'check-circle' : 'exclamation-triangle'"
|
||||
:class="theme === 'success' ? $style.icon : $style.dangerIcon"
|
||||
/>
|
||||
<div :class="$style.banner">
|
||||
<div :class="$style.content">
|
||||
<div>
|
||||
<span :class="theme === 'success' ? $style.message : $style.dangerMessage">
|
||||
{{ message }}
|
||||
</span>
|
||||
<n8n-link v-if="details && !expanded" :bold="true" size="small" @click="expand">
|
||||
<span :class="$style.moreDetails">More details</span>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot v-if="$slots.button" name="button" />
|
||||
<n8n-button
|
||||
v-else-if="buttonLabel"
|
||||
:label="buttonLoading && buttonLoadingLabel ? buttonLoadingLabel : buttonLabel"
|
||||
:title="buttonTitle"
|
||||
:type="theme"
|
||||
:loading="buttonLoading"
|
||||
size="small"
|
||||
outline
|
||||
@click.stop="onClick"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="expanded" :class="$style.details">
|
||||
{{ details }}
|
||||
</div>
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.icon {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,23 +1,3 @@
|
||||
<template>
|
||||
<div v-if="windowVisible" :class="['binary-data-window', binaryData?.fileType]">
|
||||
<n8n-button
|
||||
size="small"
|
||||
class="binary-data-window-back"
|
||||
:title="$locale.baseText('binaryDataDisplay.backToOverviewPage')"
|
||||
icon="arrow-left"
|
||||
:label="$locale.baseText('binaryDataDisplay.backToList')"
|
||||
@click.stop="closeWindow"
|
||||
/>
|
||||
|
||||
<div class="binary-data-window-wrapper">
|
||||
<div v-if="!binaryData">
|
||||
{{ $locale.baseText('binaryDataDisplay.noDataFoundToDisplay') }}
|
||||
</div>
|
||||
<BinaryDataDisplayEmbed v-else :binary-data="binaryData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { IBinaryData, IRunData } from 'n8n-workflow';
|
||||
@@ -89,6 +69,26 @@ function closeWindow() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="windowVisible" :class="['binary-data-window', binaryData?.fileType]">
|
||||
<n8n-button
|
||||
size="small"
|
||||
class="binary-data-window-back"
|
||||
:title="$locale.baseText('binaryDataDisplay.backToOverviewPage')"
|
||||
icon="arrow-left"
|
||||
:label="$locale.baseText('binaryDataDisplay.backToList')"
|
||||
@click.stop="closeWindow"
|
||||
/>
|
||||
|
||||
<div class="binary-data-window-wrapper">
|
||||
<div v-if="!binaryData">
|
||||
{{ $locale.baseText('binaryDataDisplay.noDataFoundToDisplay') }}
|
||||
</div>
|
||||
<BinaryDataDisplayEmbed v-else :binary-data="binaryData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.binary-data-window {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
<template>
|
||||
<span>
|
||||
<div v-if="isLoading">Loading binary data...</div>
|
||||
<div v-else-if="error">Error loading binary data</div>
|
||||
<span v-else>
|
||||
<video v-if="binaryData.fileType === 'video'" controls autoplay>
|
||||
<source :src="embedSource" :type="binaryData.mimeType" />
|
||||
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
|
||||
</video>
|
||||
<audio v-else-if="binaryData.fileType === 'audio'" controls autoplay>
|
||||
<source :src="embedSource" :type="binaryData.mimeType" />
|
||||
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
|
||||
</audio>
|
||||
<VueJsonPretty
|
||||
v-else-if="binaryData.fileType === 'json'"
|
||||
:data="data"
|
||||
:deep="3"
|
||||
:show-length="true"
|
||||
/>
|
||||
<RunDataHtml v-else-if="binaryData.fileType === 'html'" :input-html="data" />
|
||||
<embed v-else :src="embedSource" class="binary-data" :class="embedClass" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
@@ -75,6 +50,31 @@ onMounted(async () => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<div v-if="isLoading">Loading binary data...</div>
|
||||
<div v-else-if="error">Error loading binary data</div>
|
||||
<span v-else>
|
||||
<video v-if="binaryData.fileType === 'video'" controls autoplay>
|
||||
<source :src="embedSource" :type="binaryData.mimeType" />
|
||||
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
|
||||
</video>
|
||||
<audio v-else-if="binaryData.fileType === 'audio'" controls autoplay>
|
||||
<source :src="embedSource" :type="binaryData.mimeType" />
|
||||
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
|
||||
</audio>
|
||||
<VueJsonPretty
|
||||
v-else-if="binaryData.fileType === 'json'"
|
||||
:data="data"
|
||||
:deep="3"
|
||||
:show-length="true"
|
||||
/>
|
||||
<RunDataHtml v-else-if="binaryData.fileType === 'html'" :input-html="data" />
|
||||
<embed v-else :src="embedSource" class="binary-data" :class="embedClass" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.binary-data {
|
||||
background-color: var(--color-foreground-xlight);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<span>
|
||||
<slot :bp="bp" :value="value" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { BREAKPOINT_SM, BREAKPOINT_MD, BREAKPOINT_LG, BREAKPOINT_XL } from '@/constants';
|
||||
@@ -90,3 +84,9 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<slot :bp="bp" :value="value" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, onBeforeUnmount } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
|
||||
import { useDeviceSupport } from 'n8n-design-system';
|
||||
|
||||
const canvasStore = useCanvasStore();
|
||||
const { zoomToFit, zoomIn, zoomOut, resetZoom } = canvasStore;
|
||||
const { nodeViewScale, isDemo } = storeToRefs(canvasStore);
|
||||
const deviceSupport = useDeviceSupport();
|
||||
|
||||
const keyDown = (e: KeyboardEvent) => {
|
||||
const isCtrlKeyPressed = deviceSupport.isCtrlKeyPressed(e);
|
||||
if ((e.key === '=' || e.key === '+') && !isCtrlKeyPressed) {
|
||||
zoomIn();
|
||||
} else if ((e.key === '_' || e.key === '-') && !isCtrlKeyPressed) {
|
||||
zoomOut();
|
||||
} else if (e.key === '0' && !isCtrlKeyPressed) {
|
||||
resetZoom();
|
||||
} else if (e.key === '1' && !isCtrlKeyPressed) {
|
||||
zoomToFit();
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
document.addEventListener('keydown', keyDown);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', keyDown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
@@ -57,39 +91,6 @@
|
||||
</KeyboardShortcutTooltip>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, onBeforeUnmount } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
|
||||
import { useDeviceSupport } from 'n8n-design-system';
|
||||
|
||||
const canvasStore = useCanvasStore();
|
||||
const { zoomToFit, zoomIn, zoomOut, resetZoom } = canvasStore;
|
||||
const { nodeViewScale, isDemo } = storeToRefs(canvasStore);
|
||||
const deviceSupport = useDeviceSupport();
|
||||
|
||||
const keyDown = (e: KeyboardEvent) => {
|
||||
const isCtrlKeyPressed = deviceSupport.isCtrlKeyPressed(e);
|
||||
if ((e.key === '=' || e.key === '+') && !isCtrlKeyPressed) {
|
||||
zoomIn();
|
||||
} else if ((e.key === '_' || e.key === '-') && !isCtrlKeyPressed) {
|
||||
zoomOut();
|
||||
} else if (e.key === '0' && !isCtrlKeyPressed) {
|
||||
resetZoom();
|
||||
} else if (e.key === '1' && !isCtrlKeyPressed) {
|
||||
zoomToFit();
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
document.addEventListener('keydown', keyDown);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', keyDown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.zoomMenu {
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="CHANGE_PASSWORD_MODAL_KEY"
|
||||
:title="i18n.baseText('auth.changePassword')"
|
||||
:center="true"
|
||||
width="460px"
|
||||
:event-bus="modalBus"
|
||||
@enter="onSubmit"
|
||||
>
|
||||
<template #content>
|
||||
<n8n-form-inputs
|
||||
:inputs="config"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
@update="onInput"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:label="i18n.baseText('auth.changePassword')"
|
||||
float="right"
|
||||
data-test-id="change-password-button"
|
||||
@click="onSubmitClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
@@ -160,3 +130,33 @@ onMounted(() => {
|
||||
config.value = form;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:name="CHANGE_PASSWORD_MODAL_KEY"
|
||||
:title="i18n.baseText('auth.changePassword')"
|
||||
:center="true"
|
||||
width="460px"
|
||||
:event-bus="modalBus"
|
||||
@enter="onSubmit"
|
||||
>
|
||||
<template #content>
|
||||
<n8n-form-inputs
|
||||
:inputs="config"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
@update="onInput"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:label="i18n.baseText('auth.changePassword')"
|
||||
float="right"
|
||||
data-test-id="change-password-button"
|
||||
@click="onSubmitClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -1,51 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
ref="codeNodeEditorContainerRef"
|
||||
:class="['code-node-editor', $style['code-node-editor-container'], language]"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseout="onMouseOut"
|
||||
>
|
||||
<el-tabs
|
||||
v-if="aiEnabled"
|
||||
ref="tabs"
|
||||
v-model="activeTab"
|
||||
type="card"
|
||||
:before-leave="onBeforeTabLeave"
|
||||
>
|
||||
<el-tab-pane
|
||||
:label="$locale.baseText('codeNodeEditor.tabs.code')"
|
||||
name="code"
|
||||
data-test-id="code-node-tab-code"
|
||||
>
|
||||
<div
|
||||
ref="codeNodeEditorRef"
|
||||
:class="['ph-no-capture', 'code-editor-tabs', $style.editorInput]"
|
||||
/>
|
||||
<slot name="suffix" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
:label="$locale.baseText('codeNodeEditor.tabs.askAi')"
|
||||
name="ask-ai"
|
||||
data-test-id="code-node-tab-ai"
|
||||
>
|
||||
<!-- Key the AskAI tab to make sure it re-mounts when changing tabs -->
|
||||
<AskAI
|
||||
:key="activeTab"
|
||||
:has-changes="hasChanges"
|
||||
@replace-code="onReplaceCode"
|
||||
@started-loading="onAiLoadStart"
|
||||
@finished-loading="onAiLoadEnd"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<!-- If AskAi not enabled, there's no point in rendering tabs -->
|
||||
<div v-else :class="$style.fillHeight">
|
||||
<div ref="codeNodeEditorRef" :class="['ph-no-capture', $style.fillHeight]" />
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
@@ -411,6 +363,54 @@ function onAiLoadEnd() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="codeNodeEditorContainerRef"
|
||||
:class="['code-node-editor', $style['code-node-editor-container'], language]"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseout="onMouseOut"
|
||||
>
|
||||
<el-tabs
|
||||
v-if="aiEnabled"
|
||||
ref="tabs"
|
||||
v-model="activeTab"
|
||||
type="card"
|
||||
:before-leave="onBeforeTabLeave"
|
||||
>
|
||||
<el-tab-pane
|
||||
:label="$locale.baseText('codeNodeEditor.tabs.code')"
|
||||
name="code"
|
||||
data-test-id="code-node-tab-code"
|
||||
>
|
||||
<div
|
||||
ref="codeNodeEditorRef"
|
||||
:class="['ph-no-capture', 'code-editor-tabs', $style.editorInput]"
|
||||
/>
|
||||
<slot name="suffix" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
:label="$locale.baseText('codeNodeEditor.tabs.askAi')"
|
||||
name="ask-ai"
|
||||
data-test-id="code-node-tab-ai"
|
||||
>
|
||||
<!-- Key the AskAI tab to make sure it re-mounts when changing tabs -->
|
||||
<AskAI
|
||||
:key="activeTab"
|
||||
:has-changes="hasChanges"
|
||||
@replace-code="onReplaceCode"
|
||||
@started-loading="onAiLoadStart"
|
||||
@finished-loading="onAiLoadEnd"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<!-- If AskAi not enabled, there's no point in rendering tabs -->
|
||||
<div v-else :class="$style.fillHeight">
|
||||
<div ref="codeNodeEditorRef" :class="['ph-no-capture', $style.fillHeight]" />
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-tabs) {
|
||||
.code-editor-tabs .cm-editor {
|
||||
|
||||
@@ -1,53 +1,3 @@
|
||||
<template>
|
||||
<div class="collection-parameter" @keydown.stop>
|
||||
<div class="collection-parameter-wrapper">
|
||||
<div v-if="getProperties.length === 0" class="no-items-exist">
|
||||
<n8n-text size="small">{{ $locale.baseText('collectionParameter.noProperties') }}</n8n-text>
|
||||
</div>
|
||||
|
||||
<Suspense>
|
||||
<ParameterInputList
|
||||
:parameters="getProperties"
|
||||
:node-values="nodeValues"
|
||||
:path="path"
|
||||
:hide-delete="hideDelete"
|
||||
:indent="true"
|
||||
:is-read-only="isReadOnly"
|
||||
@value-changed="valueChanged"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
|
||||
<n8n-button
|
||||
v-if="(parameter.options ?? []).length === 1"
|
||||
type="tertiary"
|
||||
block
|
||||
:label="getPlaceholderText"
|
||||
@click="optionSelected((parameter.options ?? [])[0].name)"
|
||||
/>
|
||||
<div v-else class="add-option">
|
||||
<n8n-select
|
||||
v-model="selectedOption"
|
||||
:placeholder="getPlaceholderText"
|
||||
size="small"
|
||||
filterable
|
||||
@update:model-value="optionSelected"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="item in parameterOptions"
|
||||
:key="item.name"
|
||||
:label="getParameterOptionLabel(item)"
|
||||
:value="item.name"
|
||||
data-test-id="collection-parameter-option"
|
||||
>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import type { IUpdateInformation } from '@/Interface';
|
||||
@@ -210,6 +160,56 @@ function valueChanged(parameterData: IUpdateInformation) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="collection-parameter" @keydown.stop>
|
||||
<div class="collection-parameter-wrapper">
|
||||
<div v-if="getProperties.length === 0" class="no-items-exist">
|
||||
<n8n-text size="small">{{ $locale.baseText('collectionParameter.noProperties') }}</n8n-text>
|
||||
</div>
|
||||
|
||||
<Suspense>
|
||||
<ParameterInputList
|
||||
:parameters="getProperties"
|
||||
:node-values="nodeValues"
|
||||
:path="path"
|
||||
:hide-delete="hideDelete"
|
||||
:indent="true"
|
||||
:is-read-only="isReadOnly"
|
||||
@value-changed="valueChanged"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
|
||||
<n8n-button
|
||||
v-if="(parameter.options ?? []).length === 1"
|
||||
type="tertiary"
|
||||
block
|
||||
:label="getPlaceholderText"
|
||||
@click="optionSelected((parameter.options ?? [])[0].name)"
|
||||
/>
|
||||
<div v-else class="add-option">
|
||||
<n8n-select
|
||||
v-model="selectedOption"
|
||||
:placeholder="getPlaceholderText"
|
||||
size="small"
|
||||
filterable
|
||||
@update:model-value="optionSelected"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="item in parameterOptions"
|
||||
:key="item.name"
|
||||
:label="getParameterOptionLabel(item)"
|
||||
:value="item.name"
|
||||
data-test-id="collection-parameter-option"
|
||||
>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.collection-parameter {
|
||||
padding-left: var(--spacing-s);
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
loading: boolean;
|
||||
title?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n8n-card :class="$style.card" v-bind="$attrs">
|
||||
<template v-if="!loading && title" #header>
|
||||
@@ -10,13 +17,6 @@
|
||||
</n8n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
loading: boolean;
|
||||
title?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.card {
|
||||
min-width: 235px;
|
||||
|
||||
@@ -1,66 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.cardContainer" data-test-id="community-package-card">
|
||||
<div v-if="loading" :class="$style.cardSkeleton">
|
||||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||
</div>
|
||||
<div v-else-if="communityPackage" :class="$style.packageCard">
|
||||
<div :class="$style.cardInfoContainer">
|
||||
<div :class="$style.cardTitle">
|
||||
<n8n-text :bold="true" size="large">{{ communityPackage.packageName }}</n8n-text>
|
||||
</div>
|
||||
<div :class="$style.cardSubtitle">
|
||||
<n8n-text :bold="true" size="small" color="text-light">
|
||||
{{
|
||||
$locale.baseText('settings.communityNodes.packageNodes.label', {
|
||||
adjustToNumber: communityPackage.installedNodes.length,
|
||||
})
|
||||
}}:
|
||||
</n8n-text>
|
||||
<n8n-text size="small" color="text-light">
|
||||
<span v-for="(node, index) in communityPackage.installedNodes" :key="node.name">
|
||||
{{ node.name
|
||||
}}<span v-if="index != communityPackage.installedNodes.length - 1">,</span>
|
||||
</span>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.cardControlsContainer">
|
||||
<n8n-text :bold="true" size="large" color="text-light">
|
||||
v{{ communityPackage.installedVersion }}
|
||||
</n8n-text>
|
||||
<n8n-tooltip v-if="communityPackage.failedLoading === true" placement="top">
|
||||
<template #content>
|
||||
<div>
|
||||
{{ $locale.baseText('settings.communityNodes.failedToLoad.tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
<n8n-icon icon="exclamation-triangle" color="danger" size="large" />
|
||||
</n8n-tooltip>
|
||||
<n8n-tooltip v-else-if="communityPackage.updateAvailable" placement="top">
|
||||
<template #content>
|
||||
<div>
|
||||
{{ $locale.baseText('settings.communityNodes.updateAvailable.tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
<n8n-button outline label="Update" @click="onUpdateClick" />
|
||||
</n8n-tooltip>
|
||||
<n8n-tooltip v-else placement="top">
|
||||
<template #content>
|
||||
<div>
|
||||
{{ $locale.baseText('settings.communityNodes.upToDate.tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
<n8n-icon icon="check-circle" color="text-light" size="large" />
|
||||
</n8n-tooltip>
|
||||
<div :class="$style.cardActions">
|
||||
<n8n-action-toggle :actions="packageActions" @action="onAction"></n8n-action-toggle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import type { PublicInstalledPackage } from 'n8n-workflow';
|
||||
@@ -125,6 +62,69 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.cardContainer" data-test-id="community-package-card">
|
||||
<div v-if="loading" :class="$style.cardSkeleton">
|
||||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||
</div>
|
||||
<div v-else-if="communityPackage" :class="$style.packageCard">
|
||||
<div :class="$style.cardInfoContainer">
|
||||
<div :class="$style.cardTitle">
|
||||
<n8n-text :bold="true" size="large">{{ communityPackage.packageName }}</n8n-text>
|
||||
</div>
|
||||
<div :class="$style.cardSubtitle">
|
||||
<n8n-text :bold="true" size="small" color="text-light">
|
||||
{{
|
||||
$locale.baseText('settings.communityNodes.packageNodes.label', {
|
||||
adjustToNumber: communityPackage.installedNodes.length,
|
||||
})
|
||||
}}:
|
||||
</n8n-text>
|
||||
<n8n-text size="small" color="text-light">
|
||||
<span v-for="(node, index) in communityPackage.installedNodes" :key="node.name">
|
||||
{{ node.name
|
||||
}}<span v-if="index != communityPackage.installedNodes.length - 1">,</span>
|
||||
</span>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.cardControlsContainer">
|
||||
<n8n-text :bold="true" size="large" color="text-light">
|
||||
v{{ communityPackage.installedVersion }}
|
||||
</n8n-text>
|
||||
<n8n-tooltip v-if="communityPackage.failedLoading === true" placement="top">
|
||||
<template #content>
|
||||
<div>
|
||||
{{ $locale.baseText('settings.communityNodes.failedToLoad.tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
<n8n-icon icon="exclamation-triangle" color="danger" size="large" />
|
||||
</n8n-tooltip>
|
||||
<n8n-tooltip v-else-if="communityPackage.updateAvailable" placement="top">
|
||||
<template #content>
|
||||
<div>
|
||||
{{ $locale.baseText('settings.communityNodes.updateAvailable.tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
<n8n-button outline label="Update" @click="onUpdateClick" />
|
||||
</n8n-tooltip>
|
||||
<n8n-tooltip v-else placement="top">
|
||||
<template #content>
|
||||
<div>
|
||||
{{ $locale.baseText('settings.communityNodes.upToDate.tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
<n8n-icon icon="check-circle" color="text-light" size="large" />
|
||||
</n8n-tooltip>
|
||||
<div :class="$style.cardActions">
|
||||
<n8n-action-toggle :actions="packageActions" @action="onAction"></n8n-action-toggle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.cardContainer {
|
||||
display: flex;
|
||||
|
||||
@@ -1,95 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
width="540px"
|
||||
:name="COMMUNITY_PACKAGE_INSTALL_MODAL_KEY"
|
||||
:title="$locale.baseText('settings.communityNodes.installModal.title')"
|
||||
:event-bus="modalBus"
|
||||
:center="true"
|
||||
:before-close="onModalClose"
|
||||
:show-close="!loading"
|
||||
>
|
||||
<template #content>
|
||||
<div :class="[$style.descriptionContainer, 'p-s']">
|
||||
<div>
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('settings.communityNodes.installModal.description') }}
|
||||
</n8n-text>
|
||||
{{ ' ' }}
|
||||
<n8n-link :to="COMMUNITY_NODES_INSTALLATION_DOCS_URL" @click="onMoreInfoTopClick">
|
||||
{{ $locale.baseText('generic.moreInfo') }}
|
||||
</n8n-link>
|
||||
</div>
|
||||
<n8n-button
|
||||
:label="$locale.baseText('settings.communityNodes.browseButton.label')"
|
||||
icon="external-link-alt"
|
||||
:class="$style.browseButton"
|
||||
@click="openNPMPage"
|
||||
/>
|
||||
</div>
|
||||
<div :class="[$style.formContainer, 'mt-m']">
|
||||
<n8n-input-label
|
||||
:class="$style.labelTooltip"
|
||||
:label="$locale.baseText('settings.communityNodes.installModal.packageName.label')"
|
||||
:tooltip-text="
|
||||
$locale.baseText('settings.communityNodes.installModal.packageName.tooltip', {
|
||||
interpolate: { npmURL: NPM_KEYWORD_SEARCH_URL },
|
||||
})
|
||||
"
|
||||
>
|
||||
<n8n-input
|
||||
v-model="packageName"
|
||||
name="packageNameInput"
|
||||
type="text"
|
||||
:maxlength="214"
|
||||
:placeholder="
|
||||
$locale.baseText('settings.communityNodes.installModal.packageName.placeholder')
|
||||
"
|
||||
:required="true"
|
||||
:disabled="loading"
|
||||
@blur="onInputBlur"
|
||||
/>
|
||||
</n8n-input-label>
|
||||
<div :class="[$style.infoText, 'mt-4xs']">
|
||||
<span
|
||||
size="small"
|
||||
:class="[$style.infoText, infoTextErrorMessage ? $style.error : '']"
|
||||
v-text="infoTextErrorMessage"
|
||||
></span>
|
||||
</div>
|
||||
<el-checkbox
|
||||
v-model="userAgreed"
|
||||
:class="[$style.checkbox, checkboxWarning ? $style.error : '', 'mt-l']"
|
||||
:disabled="loading"
|
||||
data-test-id="user-agreement-checkbox"
|
||||
@update:model-value="onCheckboxChecked"
|
||||
>
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('settings.communityNodes.installModal.checkbox.label') }} </n8n-text
|
||||
><br />
|
||||
<n8n-link :to="COMMUNITY_NODES_RISKS_DOCS_URL" @click="onLearnMoreLinkClick">{{
|
||||
$locale.baseText('generic.moreInfo')
|
||||
}}</n8n-link>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:disabled="!userAgreed || packageName === '' || loading"
|
||||
:label="
|
||||
loading
|
||||
? $locale.baseText('settings.communityNodes.installModal.installButton.label.loading')
|
||||
: $locale.baseText('settings.communityNodes.installModal.installButton.label')
|
||||
"
|
||||
size="large"
|
||||
float="right"
|
||||
data-test-id="install-community-package-button"
|
||||
@click="onInstallClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
@@ -189,6 +97,98 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
width="540px"
|
||||
:name="COMMUNITY_PACKAGE_INSTALL_MODAL_KEY"
|
||||
:title="$locale.baseText('settings.communityNodes.installModal.title')"
|
||||
:event-bus="modalBus"
|
||||
:center="true"
|
||||
:before-close="onModalClose"
|
||||
:show-close="!loading"
|
||||
>
|
||||
<template #content>
|
||||
<div :class="[$style.descriptionContainer, 'p-s']">
|
||||
<div>
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('settings.communityNodes.installModal.description') }}
|
||||
</n8n-text>
|
||||
{{ ' ' }}
|
||||
<n8n-link :to="COMMUNITY_NODES_INSTALLATION_DOCS_URL" @click="onMoreInfoTopClick">
|
||||
{{ $locale.baseText('generic.moreInfo') }}
|
||||
</n8n-link>
|
||||
</div>
|
||||
<n8n-button
|
||||
:label="$locale.baseText('settings.communityNodes.browseButton.label')"
|
||||
icon="external-link-alt"
|
||||
:class="$style.browseButton"
|
||||
@click="openNPMPage"
|
||||
/>
|
||||
</div>
|
||||
<div :class="[$style.formContainer, 'mt-m']">
|
||||
<n8n-input-label
|
||||
:class="$style.labelTooltip"
|
||||
:label="$locale.baseText('settings.communityNodes.installModal.packageName.label')"
|
||||
:tooltip-text="
|
||||
$locale.baseText('settings.communityNodes.installModal.packageName.tooltip', {
|
||||
interpolate: { npmURL: NPM_KEYWORD_SEARCH_URL },
|
||||
})
|
||||
"
|
||||
>
|
||||
<n8n-input
|
||||
v-model="packageName"
|
||||
name="packageNameInput"
|
||||
type="text"
|
||||
:maxlength="214"
|
||||
:placeholder="
|
||||
$locale.baseText('settings.communityNodes.installModal.packageName.placeholder')
|
||||
"
|
||||
:required="true"
|
||||
:disabled="loading"
|
||||
@blur="onInputBlur"
|
||||
/>
|
||||
</n8n-input-label>
|
||||
<div :class="[$style.infoText, 'mt-4xs']">
|
||||
<span
|
||||
size="small"
|
||||
:class="[$style.infoText, infoTextErrorMessage ? $style.error : '']"
|
||||
v-text="infoTextErrorMessage"
|
||||
></span>
|
||||
</div>
|
||||
<el-checkbox
|
||||
v-model="userAgreed"
|
||||
:class="[$style.checkbox, checkboxWarning ? $style.error : '', 'mt-l']"
|
||||
:disabled="loading"
|
||||
data-test-id="user-agreement-checkbox"
|
||||
@update:model-value="onCheckboxChecked"
|
||||
>
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('settings.communityNodes.installModal.checkbox.label') }} </n8n-text
|
||||
><br />
|
||||
<n8n-link :to="COMMUNITY_NODES_RISKS_DOCS_URL" @click="onLearnMoreLinkClick">{{
|
||||
$locale.baseText('generic.moreInfo')
|
||||
}}</n8n-link>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:disabled="!userAgreed || packageName === '' || loading"
|
||||
:label="
|
||||
loading
|
||||
? $locale.baseText('settings.communityNodes.installModal.installButton.label.loading')
|
||||
: $locale.baseText('settings.communityNodes.installModal.installButton.label')
|
||||
"
|
||||
size="large"
|
||||
float="right"
|
||||
data-test-id="install-community-package-button"
|
||||
@click="onInstallClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.descriptionContainer {
|
||||
display: flex;
|
||||
|
||||
@@ -1,37 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
width="540px"
|
||||
:name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY"
|
||||
:title="getModalContent.title"
|
||||
:event-bus="modalBus"
|
||||
:center="true"
|
||||
:show-close="!loading"
|
||||
:before-close="onModalClose"
|
||||
>
|
||||
<template #content>
|
||||
<n8n-text>{{ getModalContent.message }}</n8n-text>
|
||||
<div
|
||||
v-if="mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE"
|
||||
:class="$style.descriptionContainer"
|
||||
>
|
||||
<n8n-info-tip theme="info" type="note" :bold="false">
|
||||
<span v-text="getModalContent.description"></span>
|
||||
</n8n-info-tip>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
:label="loading ? getModalContent.buttonLoadingLabel : getModalContent.buttonLabel"
|
||||
size="large"
|
||||
float="right"
|
||||
@click="onConfirmButtonClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import Modal from '@/components/Modal.vue';
|
||||
@@ -194,6 +160,40 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
width="540px"
|
||||
:name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY"
|
||||
:title="getModalContent.title"
|
||||
:event-bus="modalBus"
|
||||
:center="true"
|
||||
:show-close="!loading"
|
||||
:before-close="onModalClose"
|
||||
>
|
||||
<template #content>
|
||||
<n8n-text>{{ getModalContent.message }}</n8n-text>
|
||||
<div
|
||||
v-if="mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE"
|
||||
:class="$style.descriptionContainer"
|
||||
>
|
||||
<n8n-info-tip theme="info" type="note" :bold="false">
|
||||
<span v-text="getModalContent.description"></span>
|
||||
</n8n-info-tip>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
:label="loading ? getModalContent.buttonLoadingLabel : getModalContent.buttonLabel"
|
||||
size="large"
|
||||
float="right"
|
||||
@click="onConfirmButtonClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.descriptionContainer {
|
||||
display: flex;
|
||||
|
||||
@@ -1,37 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:event-bus="modalBus"
|
||||
:center="true"
|
||||
:close-on-press-escape="false"
|
||||
:before-close="closeDialog"
|
||||
custom-class="contact-prompt-modal"
|
||||
width="460px"
|
||||
>
|
||||
<template #header>
|
||||
<n8n-heading tag="h2" size="xlarge" color="text-dark">{{ title }}</n8n-heading>
|
||||
</template>
|
||||
<template #content>
|
||||
<div :class="$style.description">
|
||||
<n8n-text size="medium" color="text-base">{{ description }}</n8n-text>
|
||||
</div>
|
||||
<div @keyup.enter="send">
|
||||
<n8n-input v-model="email" placeholder="Your email address" />
|
||||
</div>
|
||||
<div :class="$style.disclaimer">
|
||||
<n8n-text size="small" color="text-base"
|
||||
>David from our product team will get in touch personally</n8n-text
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<n8n-button label="Send" float="right" :disabled="!isEmailValid" @click="send" />
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
@@ -118,6 +84,40 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:event-bus="modalBus"
|
||||
:center="true"
|
||||
:close-on-press-escape="false"
|
||||
:before-close="closeDialog"
|
||||
custom-class="contact-prompt-modal"
|
||||
width="460px"
|
||||
>
|
||||
<template #header>
|
||||
<n8n-heading tag="h2" size="xlarge" color="text-dark">{{ title }}</n8n-heading>
|
||||
</template>
|
||||
<template #content>
|
||||
<div :class="$style.description">
|
||||
<n8n-text size="medium" color="text-base">{{ description }}</n8n-text>
|
||||
</div>
|
||||
<div @keyup.enter="send">
|
||||
<n8n-input v-model="email" placeholder="Your email address" />
|
||||
</div>
|
||||
<div :class="$style.disclaimer">
|
||||
<n8n-text size="small" color="text-base"
|
||||
>David from our product team will get in touch personally</n8n-text
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<n8n-button label="Send" float="right" :disabled="!isEmailValid" @click="send" />
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.description {
|
||||
margin-bottom: var(--spacing-s);
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<n8n-input-label :label="label">
|
||||
<div
|
||||
:class="{
|
||||
[$style.copyText]: true,
|
||||
[$style[size]]: true,
|
||||
[$style.collapsed]: collapse,
|
||||
[$style.noHover]: disableCopy,
|
||||
'ph-no-capture': redactValue,
|
||||
}"
|
||||
data-test-id="copy-input"
|
||||
@click="copy"
|
||||
>
|
||||
<span ref="copyInputValue">{{ value }}</span>
|
||||
<div v-if="!disableCopy" :class="$style.copyButton">
|
||||
<span>{{ copyButtonText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</n8n-input-label>
|
||||
<div v-if="hint" :class="$style.hint">{{ hint }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@/composables/useClipboard';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
@@ -71,6 +47,30 @@ function copy() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<n8n-input-label :label="label">
|
||||
<div
|
||||
:class="{
|
||||
[$style.copyText]: true,
|
||||
[$style[size]]: true,
|
||||
[$style.collapsed]: collapse,
|
||||
[$style.noHover]: disableCopy,
|
||||
'ph-no-capture': redactValue,
|
||||
}"
|
||||
data-test-id="copy-input"
|
||||
@click="copy"
|
||||
>
|
||||
<span ref="copyInputValue">{{ value }}</span>
|
||||
<div v-if="!disableCopy" :class="$style.copyButton">
|
||||
<span>{{ copyButtonText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</n8n-input-label>
|
||||
<div v-if="hint" :class="$style.hint">{{ hint }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.copyText {
|
||||
span {
|
||||
|
||||
@@ -1,161 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="$style.config" data-test-id="node-credentials-config-container">
|
||||
<Banner
|
||||
v-show="showValidationWarning"
|
||||
theme="danger"
|
||||
:message="
|
||||
$locale.baseText(
|
||||
`credentialEdit.credentialConfig.pleaseCheckTheErrorsBelow${
|
||||
credentialPermissions.update ? '' : '.sharee'
|
||||
}`,
|
||||
{ interpolate: { owner: credentialOwnerName } },
|
||||
)
|
||||
"
|
||||
/>
|
||||
|
||||
<Banner
|
||||
v-if="authError && !showValidationWarning"
|
||||
theme="danger"
|
||||
:message="
|
||||
$locale.baseText(
|
||||
`credentialEdit.credentialConfig.couldntConnectWithTheseSettings${
|
||||
credentialPermissions.update ? '' : '.sharee'
|
||||
}`,
|
||||
{ interpolate: { owner: credentialOwnerName } },
|
||||
)
|
||||
"
|
||||
:details="authError"
|
||||
:button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
|
||||
button-loading-label="Retrying"
|
||||
:button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
|
||||
:button-loading="isRetesting"
|
||||
@click="$emit('retest')"
|
||||
/>
|
||||
|
||||
<Banner
|
||||
v-show="showOAuthSuccessBanner && !showValidationWarning"
|
||||
theme="success"
|
||||
:message="$locale.baseText('credentialEdit.credentialConfig.accountConnected')"
|
||||
:button-label="$locale.baseText('credentialEdit.credentialConfig.reconnect')"
|
||||
:button-title="
|
||||
$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')
|
||||
"
|
||||
data-test-id="oauth-connect-success-banner"
|
||||
@click="$emit('oauth')"
|
||||
>
|
||||
<template v-if="isGoogleOAuthType" #button>
|
||||
<p
|
||||
:class="$style.googleReconnectLabel"
|
||||
v-text="`${$locale.baseText('credentialEdit.credentialConfig.reconnect')}:`"
|
||||
/>
|
||||
<GoogleAuthButton @click="$emit('oauth')" />
|
||||
</template>
|
||||
</Banner>
|
||||
|
||||
<Banner
|
||||
v-show="testedSuccessfully && !showValidationWarning"
|
||||
theme="success"
|
||||
:message="$locale.baseText('credentialEdit.credentialConfig.connectionTestedSuccessfully')"
|
||||
:button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
|
||||
:button-loading-label="$locale.baseText('credentialEdit.credentialConfig.retrying')"
|
||||
:button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
|
||||
:button-loading="isRetesting"
|
||||
data-test-id="credentials-config-container-test-success"
|
||||
@click="$emit('retest')"
|
||||
/>
|
||||
|
||||
<template v-if="credentialPermissions.update">
|
||||
<n8n-notice v-if="documentationUrl && credentialProperties.length && !docs" theme="warning">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.needHelpFillingOutTheseFields') }}
|
||||
<span class="ml-4xs">
|
||||
<n8n-link :to="documentationUrl" size="small" bold @click="onDocumentationUrlClick">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.openDocs') }}
|
||||
</n8n-link>
|
||||
</span>
|
||||
</n8n-notice>
|
||||
|
||||
<AuthTypeSelector
|
||||
v-if="showAuthTypeSelector && isNewCredential"
|
||||
:credential-type="credentialType"
|
||||
@auth-type-changed="onAuthTypeChange"
|
||||
/>
|
||||
|
||||
<CopyInput
|
||||
v-if="isOAuthType && !allOAuth2BasePropertiesOverridden"
|
||||
:label="$locale.baseText('credentialEdit.credentialConfig.oAuthRedirectUrl')"
|
||||
:value="oAuthCallbackUrl"
|
||||
:copy-button-text="$locale.baseText('credentialEdit.credentialConfig.clickToCopy')"
|
||||
:hint="
|
||||
$locale.baseText('credentialEdit.credentialConfig.subtitle', {
|
||||
interpolate: { appName },
|
||||
})
|
||||
"
|
||||
:toast-title="
|
||||
$locale.baseText('credentialEdit.credentialConfig.redirectUrlCopiedToClipboard')
|
||||
"
|
||||
:redact-value="true"
|
||||
/>
|
||||
</template>
|
||||
<EnterpriseEdition v-else :features="[EnterpriseEditionFeature.Sharing]">
|
||||
<div>
|
||||
<n8n-info-tip :bold="false">
|
||||
{{
|
||||
$locale.baseText('credentialEdit.credentialEdit.info.sharee', {
|
||||
interpolate: { credentialOwnerName },
|
||||
})
|
||||
}}
|
||||
</n8n-info-tip>
|
||||
</div>
|
||||
</EnterpriseEdition>
|
||||
|
||||
<CredentialInputs
|
||||
v-if="credentialType && credentialPermissions.update"
|
||||
:credential-data="credentialData"
|
||||
:credential-properties="credentialProperties"
|
||||
:documentation-url="documentationUrl"
|
||||
:show-validation-warnings="showValidationWarning"
|
||||
@update="onDataChange"
|
||||
/>
|
||||
|
||||
<OauthButton
|
||||
v-if="
|
||||
isOAuthType &&
|
||||
requiredPropertiesFilled &&
|
||||
!isOAuthConnected &&
|
||||
credentialPermissions.update
|
||||
"
|
||||
:is-google-o-auth-type="isGoogleOAuthType"
|
||||
data-test-id="oauth-connect-button"
|
||||
@click="$emit('oauth')"
|
||||
/>
|
||||
|
||||
<n8n-text v-if="isMissingCredentials" color="text-base" size="medium">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.missingCredentialType') }}
|
||||
</n8n-text>
|
||||
|
||||
<EnterpriseEdition :features="[EnterpriseEditionFeature.ExternalSecrets]">
|
||||
<template #fallback>
|
||||
<n8n-info-tip class="mt-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.externalSecrets') }}
|
||||
<n8n-link bold :to="$locale.baseText('settings.externalSecrets.docs')" size="small">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.externalSecrets.moreInfo') }}
|
||||
</n8n-link>
|
||||
</n8n-info-tip>
|
||||
</template>
|
||||
</EnterpriseEdition>
|
||||
</div>
|
||||
<CredentialDocs
|
||||
v-if="docs"
|
||||
:credential-type="credentialType"
|
||||
:documentation-url="documentationUrl"
|
||||
:docs="docs"
|
||||
:class="$style.docs"
|
||||
>
|
||||
</CredentialDocs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, watch } from 'vue';
|
||||
|
||||
@@ -345,6 +187,164 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div :class="$style.config" data-test-id="node-credentials-config-container">
|
||||
<Banner
|
||||
v-show="showValidationWarning"
|
||||
theme="danger"
|
||||
:message="
|
||||
$locale.baseText(
|
||||
`credentialEdit.credentialConfig.pleaseCheckTheErrorsBelow${
|
||||
credentialPermissions.update ? '' : '.sharee'
|
||||
}`,
|
||||
{ interpolate: { owner: credentialOwnerName } },
|
||||
)
|
||||
"
|
||||
/>
|
||||
|
||||
<Banner
|
||||
v-if="authError && !showValidationWarning"
|
||||
theme="danger"
|
||||
:message="
|
||||
$locale.baseText(
|
||||
`credentialEdit.credentialConfig.couldntConnectWithTheseSettings${
|
||||
credentialPermissions.update ? '' : '.sharee'
|
||||
}`,
|
||||
{ interpolate: { owner: credentialOwnerName } },
|
||||
)
|
||||
"
|
||||
:details="authError"
|
||||
:button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
|
||||
button-loading-label="Retrying"
|
||||
:button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
|
||||
:button-loading="isRetesting"
|
||||
@click="$emit('retest')"
|
||||
/>
|
||||
|
||||
<Banner
|
||||
v-show="showOAuthSuccessBanner && !showValidationWarning"
|
||||
theme="success"
|
||||
:message="$locale.baseText('credentialEdit.credentialConfig.accountConnected')"
|
||||
:button-label="$locale.baseText('credentialEdit.credentialConfig.reconnect')"
|
||||
:button-title="
|
||||
$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')
|
||||
"
|
||||
data-test-id="oauth-connect-success-banner"
|
||||
@click="$emit('oauth')"
|
||||
>
|
||||
<template v-if="isGoogleOAuthType" #button>
|
||||
<p
|
||||
:class="$style.googleReconnectLabel"
|
||||
v-text="`${$locale.baseText('credentialEdit.credentialConfig.reconnect')}:`"
|
||||
/>
|
||||
<GoogleAuthButton @click="$emit('oauth')" />
|
||||
</template>
|
||||
</Banner>
|
||||
|
||||
<Banner
|
||||
v-show="testedSuccessfully && !showValidationWarning"
|
||||
theme="success"
|
||||
:message="$locale.baseText('credentialEdit.credentialConfig.connectionTestedSuccessfully')"
|
||||
:button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
|
||||
:button-loading-label="$locale.baseText('credentialEdit.credentialConfig.retrying')"
|
||||
:button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
|
||||
:button-loading="isRetesting"
|
||||
data-test-id="credentials-config-container-test-success"
|
||||
@click="$emit('retest')"
|
||||
/>
|
||||
|
||||
<template v-if="credentialPermissions.update">
|
||||
<n8n-notice v-if="documentationUrl && credentialProperties.length && !docs" theme="warning">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.needHelpFillingOutTheseFields') }}
|
||||
<span class="ml-4xs">
|
||||
<n8n-link :to="documentationUrl" size="small" bold @click="onDocumentationUrlClick">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.openDocs') }}
|
||||
</n8n-link>
|
||||
</span>
|
||||
</n8n-notice>
|
||||
|
||||
<AuthTypeSelector
|
||||
v-if="showAuthTypeSelector && isNewCredential"
|
||||
:credential-type="credentialType"
|
||||
@auth-type-changed="onAuthTypeChange"
|
||||
/>
|
||||
|
||||
<CopyInput
|
||||
v-if="isOAuthType && !allOAuth2BasePropertiesOverridden"
|
||||
:label="$locale.baseText('credentialEdit.credentialConfig.oAuthRedirectUrl')"
|
||||
:value="oAuthCallbackUrl"
|
||||
:copy-button-text="$locale.baseText('credentialEdit.credentialConfig.clickToCopy')"
|
||||
:hint="
|
||||
$locale.baseText('credentialEdit.credentialConfig.subtitle', {
|
||||
interpolate: { appName },
|
||||
})
|
||||
"
|
||||
:toast-title="
|
||||
$locale.baseText('credentialEdit.credentialConfig.redirectUrlCopiedToClipboard')
|
||||
"
|
||||
:redact-value="true"
|
||||
/>
|
||||
</template>
|
||||
<EnterpriseEdition v-else :features="[EnterpriseEditionFeature.Sharing]">
|
||||
<div>
|
||||
<n8n-info-tip :bold="false">
|
||||
{{
|
||||
$locale.baseText('credentialEdit.credentialEdit.info.sharee', {
|
||||
interpolate: { credentialOwnerName },
|
||||
})
|
||||
}}
|
||||
</n8n-info-tip>
|
||||
</div>
|
||||
</EnterpriseEdition>
|
||||
|
||||
<CredentialInputs
|
||||
v-if="credentialType && credentialPermissions.update"
|
||||
:credential-data="credentialData"
|
||||
:credential-properties="credentialProperties"
|
||||
:documentation-url="documentationUrl"
|
||||
:show-validation-warnings="showValidationWarning"
|
||||
@update="onDataChange"
|
||||
/>
|
||||
|
||||
<OauthButton
|
||||
v-if="
|
||||
isOAuthType &&
|
||||
requiredPropertiesFilled &&
|
||||
!isOAuthConnected &&
|
||||
credentialPermissions.update
|
||||
"
|
||||
:is-google-o-auth-type="isGoogleOAuthType"
|
||||
data-test-id="oauth-connect-button"
|
||||
@click="$emit('oauth')"
|
||||
/>
|
||||
|
||||
<n8n-text v-if="isMissingCredentials" color="text-base" size="medium">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.missingCredentialType') }}
|
||||
</n8n-text>
|
||||
|
||||
<EnterpriseEdition :features="[EnterpriseEditionFeature.ExternalSecrets]">
|
||||
<template #fallback>
|
||||
<n8n-info-tip class="mt-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.externalSecrets') }}
|
||||
<n8n-link bold :to="$locale.baseText('settings.externalSecrets.docs')" size="small">
|
||||
{{ $locale.baseText('credentialEdit.credentialConfig.externalSecrets.moreInfo') }}
|
||||
</n8n-link>
|
||||
</n8n-info-tip>
|
||||
</template>
|
||||
</EnterpriseEdition>
|
||||
</div>
|
||||
<CredentialDocs
|
||||
v-if="docs"
|
||||
:credential-type="credentialType"
|
||||
:documentation-url="documentationUrl"
|
||||
:docs="docs"
|
||||
:class="$style.docs"
|
||||
>
|
||||
</CredentialDocs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.config {
|
||||
--notice-margin: 0;
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.docs">
|
||||
<div :class="$style.header">
|
||||
<p :class="$style.title">{{ i18n.baseText('credentialEdit.credentialEdit.setupGuide') }}</p>
|
||||
<n8n-link
|
||||
:class="$style.docsLink"
|
||||
theme="text"
|
||||
new-window
|
||||
:to="documentationUrl"
|
||||
@click="onDocumentationUrlClick"
|
||||
>
|
||||
{{ i18n.baseText('credentialEdit.credentialEdit.docs') }}
|
||||
<n8n-icon icon="external-link-alt" size="small" :class="$style.externalIcon" />
|
||||
</n8n-link>
|
||||
</div>
|
||||
<VueMarkdown :source="docs" :options="{ html: true }" :class="$style.markdown" />
|
||||
<Feedback
|
||||
:class="$style.feedback"
|
||||
:model-value="submittedFeedback"
|
||||
@update:model-value="onFeedback"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Feedback from '@/components/Feedback.vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
@@ -66,6 +42,30 @@ function onDocumentationUrlClick(): void {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.docs">
|
||||
<div :class="$style.header">
|
||||
<p :class="$style.title">{{ i18n.baseText('credentialEdit.credentialEdit.setupGuide') }}</p>
|
||||
<n8n-link
|
||||
:class="$style.docsLink"
|
||||
theme="text"
|
||||
new-window
|
||||
:to="documentationUrl"
|
||||
@click="onDocumentationUrlClick"
|
||||
>
|
||||
{{ i18n.baseText('credentialEdit.credentialEdit.docs') }}
|
||||
<n8n-icon icon="external-link-alt" size="small" :class="$style.externalIcon" />
|
||||
</n8n-link>
|
||||
</div>
|
||||
<VueMarkdown :source="docs" :options="{ html: true }" :class="$style.markdown" />
|
||||
<Feedback
|
||||
:class="$style.feedback"
|
||||
:model-value="submittedFeedback"
|
||||
@update:model-value="onFeedback"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.docs {
|
||||
background-color: var(--color-background-light);
|
||||
|
||||
@@ -1,118 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:custom-class="$style.credentialModal"
|
||||
:event-bus="modalBus"
|
||||
:loading="loading"
|
||||
:before-close="beforeClose"
|
||||
width="70%"
|
||||
height="80%"
|
||||
>
|
||||
<template #header>
|
||||
<div :class="$style.header">
|
||||
<div :class="$style.credInfo">
|
||||
<div :class="$style.credIcon">
|
||||
<CredentialIcon :credential-type-name="defaultCredentialTypeName" />
|
||||
</div>
|
||||
<InlineNameEdit
|
||||
:model-value="credentialName"
|
||||
:subtitle="credentialType ? credentialType.displayName : ''"
|
||||
:readonly="!credentialPermissions.update || !credentialType"
|
||||
type="Credential"
|
||||
data-test-id="credential-name"
|
||||
@update:model-value="onNameEdit"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.credActions">
|
||||
<n8n-icon-button
|
||||
v-if="currentCredential && credentialPermissions.delete"
|
||||
:title="$locale.baseText('credentialEdit.credentialEdit.delete')"
|
||||
icon="trash"
|
||||
type="tertiary"
|
||||
:disabled="isSaving"
|
||||
:loading="isDeleting"
|
||||
data-test-id="credential-delete-button"
|
||||
@click="deleteCredential"
|
||||
/>
|
||||
<SaveButton
|
||||
v-if="showSaveButton"
|
||||
:saved="!hasUnsavedChanges && !isTesting"
|
||||
:is-saving="isSaving || isTesting"
|
||||
:saving-label="
|
||||
isTesting
|
||||
? $locale.baseText('credentialEdit.credentialEdit.testing')
|
||||
: $locale.baseText('credentialEdit.credentialEdit.saving')
|
||||
"
|
||||
data-test-id="credential-save-button"
|
||||
@click="saveCredential"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div :class="$style.container" data-test-id="credential-edit-dialog">
|
||||
<div :class="$style.sidebar">
|
||||
<n8n-menu
|
||||
mode="tabs"
|
||||
:items="sidebarItems"
|
||||
:transparent-background="true"
|
||||
@select="onTabSelect"
|
||||
></n8n-menu>
|
||||
</div>
|
||||
<div
|
||||
v-if="activeTab === 'connection' && credentialType"
|
||||
ref="contentRef"
|
||||
:class="$style.mainContent"
|
||||
>
|
||||
<CredentialConfig
|
||||
:credential-type="credentialType"
|
||||
:credential-properties="credentialProperties"
|
||||
:credential-data="credentialData"
|
||||
:credential-id="credentialId"
|
||||
:show-validation-warning="showValidationWarning"
|
||||
:auth-error="authError"
|
||||
:tested-successfully="testedSuccessfully"
|
||||
:is-o-auth-type="isOAuthType"
|
||||
:is-o-auth-connected="isOAuthConnected"
|
||||
:is-retesting="isRetesting"
|
||||
:parent-types="parentTypes"
|
||||
:required-properties-filled="requiredPropertiesFilled"
|
||||
:credential-permissions="credentialPermissions"
|
||||
:all-o-auth2-base-properties-overridden="allOAuth2BasePropertiesOverridden"
|
||||
:mode="mode"
|
||||
:selected-credential="selectedCredential"
|
||||
:show-auth-type-selector="requiredCredentials"
|
||||
@update="onDataChange"
|
||||
@oauth="oAuthCredentialAuthorize"
|
||||
@retest="retestCredential"
|
||||
@scroll-to-top="scrollToTop"
|
||||
@auth-type-changed="onAuthTypeChanged"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="showSharingContent" :class="$style.mainContent">
|
||||
<CredentialSharing
|
||||
:credential="currentCredential"
|
||||
:credential-data="credentialData"
|
||||
:credential-id="credentialId"
|
||||
:credential-permissions="credentialPermissions"
|
||||
:modal-bus="modalBus"
|
||||
@update:model-value="onChangeSharedWith"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'details' && credentialType" :class="$style.mainContent">
|
||||
<CredentialInfo
|
||||
:current-credential="currentCredential"
|
||||
:credential-permissions="credentialPermissions"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab.startsWith('coming-soon')" :class="$style.mainContent">
|
||||
<FeatureComingSoon :feature-id="activeTab.split('/')[1]"></FeatureComingSoon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
@@ -1143,6 +1028,121 @@ function resetCredentialData(): void {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:custom-class="$style.credentialModal"
|
||||
:event-bus="modalBus"
|
||||
:loading="loading"
|
||||
:before-close="beforeClose"
|
||||
width="70%"
|
||||
height="80%"
|
||||
>
|
||||
<template #header>
|
||||
<div :class="$style.header">
|
||||
<div :class="$style.credInfo">
|
||||
<div :class="$style.credIcon">
|
||||
<CredentialIcon :credential-type-name="defaultCredentialTypeName" />
|
||||
</div>
|
||||
<InlineNameEdit
|
||||
:model-value="credentialName"
|
||||
:subtitle="credentialType ? credentialType.displayName : ''"
|
||||
:readonly="!credentialPermissions.update || !credentialType"
|
||||
type="Credential"
|
||||
data-test-id="credential-name"
|
||||
@update:model-value="onNameEdit"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.credActions">
|
||||
<n8n-icon-button
|
||||
v-if="currentCredential && credentialPermissions.delete"
|
||||
:title="$locale.baseText('credentialEdit.credentialEdit.delete')"
|
||||
icon="trash"
|
||||
type="tertiary"
|
||||
:disabled="isSaving"
|
||||
:loading="isDeleting"
|
||||
data-test-id="credential-delete-button"
|
||||
@click="deleteCredential"
|
||||
/>
|
||||
<SaveButton
|
||||
v-if="showSaveButton"
|
||||
:saved="!hasUnsavedChanges && !isTesting"
|
||||
:is-saving="isSaving || isTesting"
|
||||
:saving-label="
|
||||
isTesting
|
||||
? $locale.baseText('credentialEdit.credentialEdit.testing')
|
||||
: $locale.baseText('credentialEdit.credentialEdit.saving')
|
||||
"
|
||||
data-test-id="credential-save-button"
|
||||
@click="saveCredential"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div :class="$style.container" data-test-id="credential-edit-dialog">
|
||||
<div :class="$style.sidebar">
|
||||
<n8n-menu
|
||||
mode="tabs"
|
||||
:items="sidebarItems"
|
||||
:transparent-background="true"
|
||||
@select="onTabSelect"
|
||||
></n8n-menu>
|
||||
</div>
|
||||
<div
|
||||
v-if="activeTab === 'connection' && credentialType"
|
||||
ref="contentRef"
|
||||
:class="$style.mainContent"
|
||||
>
|
||||
<CredentialConfig
|
||||
:credential-type="credentialType"
|
||||
:credential-properties="credentialProperties"
|
||||
:credential-data="credentialData"
|
||||
:credential-id="credentialId"
|
||||
:show-validation-warning="showValidationWarning"
|
||||
:auth-error="authError"
|
||||
:tested-successfully="testedSuccessfully"
|
||||
:is-o-auth-type="isOAuthType"
|
||||
:is-o-auth-connected="isOAuthConnected"
|
||||
:is-retesting="isRetesting"
|
||||
:parent-types="parentTypes"
|
||||
:required-properties-filled="requiredPropertiesFilled"
|
||||
:credential-permissions="credentialPermissions"
|
||||
:all-o-auth2-base-properties-overridden="allOAuth2BasePropertiesOverridden"
|
||||
:mode="mode"
|
||||
:selected-credential="selectedCredential"
|
||||
:show-auth-type-selector="requiredCredentials"
|
||||
@update="onDataChange"
|
||||
@oauth="oAuthCredentialAuthorize"
|
||||
@retest="retestCredential"
|
||||
@scroll-to-top="scrollToTop"
|
||||
@auth-type-changed="onAuthTypeChanged"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="showSharingContent" :class="$style.mainContent">
|
||||
<CredentialSharing
|
||||
:credential="currentCredential"
|
||||
:credential-data="credentialData"
|
||||
:credential-id="credentialId"
|
||||
:credential-permissions="credentialPermissions"
|
||||
:modal-bus="modalBus"
|
||||
@update:model-value="onChangeSharedWith"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'details' && credentialType" :class="$style.mainContent">
|
||||
<CredentialInfo
|
||||
:current-credential="currentCredential"
|
||||
:credential-permissions="credentialPermissions"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab.startsWith('coming-soon')" :class="$style.mainContent">
|
||||
<FeatureComingSoon :feature-id="activeTab.split('/')[1]"></FeatureComingSoon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.credentialModal {
|
||||
--dialog-max-width: 1200px;
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import TimeAgo from '../TimeAgo.vue';
|
||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialInfo',
|
||||
components: {
|
||||
TimeAgo,
|
||||
},
|
||||
props: ['currentCredential', 'credentialPermissions'],
|
||||
methods: {
|
||||
shortNodeType(nodeType: INodeTypeDescription) {
|
||||
return this.$locale.shortNodeType(nodeType.name);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<el-row v-if="currentCredential">
|
||||
@@ -37,26 +57,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import TimeAgo from '../TimeAgo.vue';
|
||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialInfo',
|
||||
components: {
|
||||
TimeAgo,
|
||||
},
|
||||
props: ['currentCredential', 'credentialPermissions'],
|
||||
methods: {
|
||||
shortNodeType(nodeType: INodeTypeDescription) {
|
||||
return this.$locale.shortNodeType(nodeType.name);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
> * {
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
<template>
|
||||
<div v-if="credentialProperties.length" :class="$style.container" @keydown.stop>
|
||||
<form
|
||||
v-for="parameter in credentialProperties"
|
||||
:key="parameter.name"
|
||||
autocomplete="off"
|
||||
data-test-id="credential-connection-parameter"
|
||||
@submit.prevent
|
||||
>
|
||||
<!-- Why form? to break up inputs, to prevent Chrome autofill -->
|
||||
<n8n-notice v-if="parameter.type === 'notice'" :content="parameter.displayName" />
|
||||
<ParameterInputExpanded
|
||||
v-else
|
||||
:parameter="parameter"
|
||||
:value="credentialDataValues[parameter.name]"
|
||||
:documentation-url="documentationUrl"
|
||||
:show-validation-warnings="showValidationWarnings"
|
||||
:label="{ size: 'medium' }"
|
||||
event-source="credentials"
|
||||
@update="valueChanged"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
@@ -60,6 +35,31 @@ function valueChanged(parameterData: IUpdateInformation) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="credentialProperties.length" :class="$style.container" @keydown.stop>
|
||||
<form
|
||||
v-for="parameter in credentialProperties"
|
||||
:key="parameter.name"
|
||||
autocomplete="off"
|
||||
data-test-id="credential-connection-parameter"
|
||||
@submit.prevent
|
||||
>
|
||||
<!-- Why form? to break up inputs, to prevent Chrome autofill -->
|
||||
<n8n-notice v-if="parameter.type === 'notice'" :content="parameter.displayName" />
|
||||
<ParameterInputExpanded
|
||||
v-else
|
||||
:parameter="parameter"
|
||||
:value="credentialDataValues[parameter.name]"
|
||||
:documentation-url="documentationUrl"
|
||||
:show-validation-warnings="showValidationWarnings"
|
||||
:label="{ size: 'medium' }"
|
||||
event-source="credentials"
|
||||
@update="valueChanged"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
> * {
|
||||
|
||||
@@ -1,52 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<div v-if="!isSharingEnabled">
|
||||
<N8nActionBox
|
||||
:heading="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.title,
|
||||
)
|
||||
"
|
||||
:description="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.description,
|
||||
)
|
||||
"
|
||||
:button-text="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.button,
|
||||
)
|
||||
"
|
||||
@click:button="goToUpgrade"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<N8nInfoTip v-if="credentialPermissions.share" :bold="false" class="mb-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialSharing.info.owner') }}
|
||||
</N8nInfoTip>
|
||||
<N8nInfoTip v-else-if="isHomeTeamProject" :bold="false" class="mb-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialSharing.info.sharee.team') }}
|
||||
</N8nInfoTip>
|
||||
<N8nInfoTip v-else :bold="false" class="mb-s">
|
||||
{{
|
||||
$locale.baseText('credentialEdit.credentialSharing.info.sharee.personal', {
|
||||
interpolate: { credentialOwnerName },
|
||||
})
|
||||
}}
|
||||
</N8nInfoTip>
|
||||
<ProjectSharing
|
||||
v-model="sharedWithProjects"
|
||||
:projects="projects"
|
||||
:roles="credentialRoles"
|
||||
:home-project="homeProject"
|
||||
:readonly="!credentialPermissions.share"
|
||||
:static="!credentialPermissions.share"
|
||||
:placeholder="sharingSelectPlaceholder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type {
|
||||
ICredentialsResponse,
|
||||
@@ -208,6 +159,55 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<div v-if="!isSharingEnabled">
|
||||
<N8nActionBox
|
||||
:heading="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.title,
|
||||
)
|
||||
"
|
||||
:description="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.description,
|
||||
)
|
||||
"
|
||||
:button-text="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.button,
|
||||
)
|
||||
"
|
||||
@click:button="goToUpgrade"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<N8nInfoTip v-if="credentialPermissions.share" :bold="false" class="mb-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialSharing.info.owner') }}
|
||||
</N8nInfoTip>
|
||||
<N8nInfoTip v-else-if="isHomeTeamProject" :bold="false" class="mb-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialSharing.info.sharee.team') }}
|
||||
</N8nInfoTip>
|
||||
<N8nInfoTip v-else :bold="false" class="mb-s">
|
||||
{{
|
||||
$locale.baseText('credentialEdit.credentialSharing.info.sharee.personal', {
|
||||
interpolate: { credentialOwnerName },
|
||||
})
|
||||
}}
|
||||
</N8nInfoTip>
|
||||
<ProjectSharing
|
||||
v-model="sharedWithProjects"
|
||||
:projects="projects"
|
||||
:roles="credentialRoles"
|
||||
:home-project="homeProject"
|
||||
:readonly="!credentialPermissions.share"
|
||||
:static="!credentialPermissions.share"
|
||||
:placeholder="sharingSelectPlaceholder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
<template>
|
||||
<button
|
||||
:class="$style.googleAuthBtn"
|
||||
:title="$locale.baseText('credentialEdit.oAuthButton.signInWithGoogle')"
|
||||
:style="googleAuthButtons"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
@@ -20,6 +12,14 @@ const googleAuthButtons = {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:class="$style.googleAuthBtn"
|
||||
:title="$locale.baseText('credentialEdit.oAuthButton.signInWithGoogle')"
|
||||
:style="googleAuthButtons"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.googleAuthBtn {
|
||||
--google-auth-btn-height: 46px;
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import GoogleAuthButton from './GoogleAuthButton.vue';
|
||||
|
||||
defineProps<{
|
||||
isGoogleOAuthType: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<GoogleAuthButton v-if="isGoogleOAuthType" />
|
||||
@@ -9,14 +17,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import GoogleAuthButton from './GoogleAuthButton.vue';
|
||||
|
||||
defineProps<{
|
||||
isGoogleOAuthType: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
display: inline-block;
|
||||
|
||||
@@ -1,53 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="$style['parameter-value-container']">
|
||||
<n8n-select
|
||||
ref="innerSelect"
|
||||
:size="inputSize"
|
||||
filterable
|
||||
:model-value="displayValue"
|
||||
:placeholder="$locale.baseText('parameterInput.select')"
|
||||
:title="displayTitle"
|
||||
:disabled="isReadOnly"
|
||||
data-test-id="credential-select"
|
||||
@update:model-value="(value: string) => $emit('update:modelValue', value)"
|
||||
@keydown.stop
|
||||
@focus="$emit('setFocus')"
|
||||
@blur="$emit('onBlur')"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="credType in supportedCredentialTypes"
|
||||
:key="credType.name"
|
||||
:value="credType.name"
|
||||
:label="credType.displayName"
|
||||
data-test-id="credential-select-option"
|
||||
>
|
||||
<div class="list-option">
|
||||
<div class="option-headline">
|
||||
{{ credType.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
<slot name="issues-and-options" />
|
||||
</div>
|
||||
|
||||
<ScopesNotice
|
||||
v-if="scopes.length > 0"
|
||||
:active-credential-type="activeCredentialType"
|
||||
:scopes="scopes"
|
||||
/>
|
||||
<div>
|
||||
<NodeCredentials
|
||||
:node="node"
|
||||
:readonly="isReadOnly"
|
||||
:override-cred-type="node.parameters[parameter.name]"
|
||||
@credential-selected="(updateInformation) => $emit('credentialSelected', updateInformation)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { ICredentialType } from 'n8n-workflow';
|
||||
import { defineComponent } from 'vue';
|
||||
@@ -156,6 +106,56 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div :class="$style['parameter-value-container']">
|
||||
<n8n-select
|
||||
ref="innerSelect"
|
||||
:size="inputSize"
|
||||
filterable
|
||||
:model-value="displayValue"
|
||||
:placeholder="$locale.baseText('parameterInput.select')"
|
||||
:title="displayTitle"
|
||||
:disabled="isReadOnly"
|
||||
data-test-id="credential-select"
|
||||
@update:model-value="(value: string) => $emit('update:modelValue', value)"
|
||||
@keydown.stop
|
||||
@focus="$emit('setFocus')"
|
||||
@blur="$emit('onBlur')"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="credType in supportedCredentialTypes"
|
||||
:key="credType.name"
|
||||
:value="credType.name"
|
||||
:label="credType.displayName"
|
||||
data-test-id="credential-select-option"
|
||||
>
|
||||
<div class="list-option">
|
||||
<div class="option-headline">
|
||||
{{ credType.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
<slot name="issues-and-options" />
|
||||
</div>
|
||||
|
||||
<ScopesNotice
|
||||
v-if="scopes.length > 0"
|
||||
:active-credential-type="activeCredentialType"
|
||||
:scopes="scopes"
|
||||
/>
|
||||
<div>
|
||||
<NodeCredentials
|
||||
:node="node"
|
||||
:readonly="isReadOnly"
|
||||
:override-cred-type="node.parameters[parameter.name]"
|
||||
@credential-selected="(updateInformation) => $emit('credentialSelected', updateInformation)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.parameter-value-container {
|
||||
display: flex;
|
||||
|
||||
@@ -1,62 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="CREDENTIAL_SELECT_MODAL_KEY"
|
||||
:event-bus="modalBus"
|
||||
width="50%"
|
||||
:center="true"
|
||||
:loading="loading"
|
||||
max-width="460px"
|
||||
min-height="250px"
|
||||
>
|
||||
<template #header>
|
||||
<h2 :class="$style.title">
|
||||
{{ $locale.baseText('credentialSelectModal.addNewCredential') }}
|
||||
</h2>
|
||||
</template>
|
||||
<template #content>
|
||||
<div>
|
||||
<div :class="$style.subtitle">
|
||||
{{ $locale.baseText('credentialSelectModal.selectAnAppOrServiceToConnectTo') }}
|
||||
</div>
|
||||
<n8n-select
|
||||
ref="select"
|
||||
filterable
|
||||
default-first-option
|
||||
:placeholder="$locale.baseText('credentialSelectModal.searchForApp')"
|
||||
size="xlarge"
|
||||
:model-value="selected"
|
||||
data-test-id="new-credential-type-select"
|
||||
@update:model-value="onSelect"
|
||||
>
|
||||
<template #prefix>
|
||||
<font-awesome-icon icon="search" />
|
||||
</template>
|
||||
<n8n-option
|
||||
v-for="credential in credentialsStore.allCredentialTypes"
|
||||
:key="credential.name"
|
||||
:value="credential.name"
|
||||
:label="credential.displayName"
|
||||
filterable
|
||||
data-test-id="new-credential-type-select-option"
|
||||
/>
|
||||
</n8n-select>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<n8n-button
|
||||
:label="$locale.baseText('credentialSelectModal.continue')"
|
||||
float="right"
|
||||
size="large"
|
||||
:disabled="!selected"
|
||||
data-test-id="new-credential-type-button"
|
||||
@click="openCredentialType"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import Modal from './Modal.vue';
|
||||
@@ -125,6 +66,65 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:name="CREDENTIAL_SELECT_MODAL_KEY"
|
||||
:event-bus="modalBus"
|
||||
width="50%"
|
||||
:center="true"
|
||||
:loading="loading"
|
||||
max-width="460px"
|
||||
min-height="250px"
|
||||
>
|
||||
<template #header>
|
||||
<h2 :class="$style.title">
|
||||
{{ $locale.baseText('credentialSelectModal.addNewCredential') }}
|
||||
</h2>
|
||||
</template>
|
||||
<template #content>
|
||||
<div>
|
||||
<div :class="$style.subtitle">
|
||||
{{ $locale.baseText('credentialSelectModal.selectAnAppOrServiceToConnectTo') }}
|
||||
</div>
|
||||
<n8n-select
|
||||
ref="select"
|
||||
filterable
|
||||
default-first-option
|
||||
:placeholder="$locale.baseText('credentialSelectModal.searchForApp')"
|
||||
size="xlarge"
|
||||
:model-value="selected"
|
||||
data-test-id="new-credential-type-select"
|
||||
@update:model-value="onSelect"
|
||||
>
|
||||
<template #prefix>
|
||||
<font-awesome-icon icon="search" />
|
||||
</template>
|
||||
<n8n-option
|
||||
v-for="credential in credentialsStore.allCredentialTypes"
|
||||
:key="credential.name"
|
||||
:value="credential.name"
|
||||
:label="credential.displayName"
|
||||
filterable
|
||||
data-test-id="new-credential-type-select-option"
|
||||
/>
|
||||
</n8n-select>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<n8n-button
|
||||
:label="$locale.baseText('credentialSelectModal.continue')"
|
||||
float="right"
|
||||
size="large"
|
||||
:disabled="!selected"
|
||||
data-test-id="new-credential-type-button"
|
||||
@click="openCredentialType"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.title {
|
||||
font-size: var(--font-size-xl);
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
ref="wrapper"
|
||||
:class="{ [$style.dragging]: isDragging }"
|
||||
@mousedown="onDragStart"
|
||||
>
|
||||
<slot :is-dragging="isDragging"></slot>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-show="isDragging" ref="draggable" :class="$style.draggable" :style="draggableStyle">
|
||||
<slot name="preview" :can-drop="canDrop" :el="draggingElement"></slot>
|
||||
</div>
|
||||
</Teleport>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XYPosition } from '@/Interface';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
@@ -137,6 +120,23 @@ const onDragEnd = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
ref="wrapper"
|
||||
:class="{ [$style.dragging]: isDragging }"
|
||||
@mousedown="onDragStart"
|
||||
>
|
||||
<slot :is-dragging="isDragging"></slot>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-show="isDragging" ref="draggable" :class="$style.draggable" :style="draggableStyle">
|
||||
<slot name="preview" :can-drop="canDrop" :el="draggingElement"></slot>
|
||||
</div>
|
||||
</Teleport>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.dragging {
|
||||
visibility: visible;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<div ref="targetRef" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mouseup="onMouseUp">
|
||||
<slot :droppable="droppable" :active-drop="activeDrop"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XYPosition } from '@/Interface';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
@@ -84,3 +78,9 @@ function getStickyPosition(): XYPosition | null {
|
||||
return [left + props.stickyOffset[0], top + props.stickyOffset[1]];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="targetRef" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mouseup="onMouseUp">
|
||||
<slot :droppable="droppable" :active-drop="activeDrop"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,52 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:event-bus="modalBus"
|
||||
:title="$locale.baseText('duplicateWorkflowDialog.duplicateWorkflow')"
|
||||
:center="true"
|
||||
width="420px"
|
||||
@enter="save"
|
||||
>
|
||||
<template #content>
|
||||
<div :class="$style.content">
|
||||
<n8n-input
|
||||
ref="nameInput"
|
||||
v-model="name"
|
||||
:placeholder="$locale.baseText('duplicateWorkflowDialog.enterWorkflowName')"
|
||||
:maxlength="MAX_WORKFLOW_NAME_LENGTH"
|
||||
/>
|
||||
<TagsDropdown
|
||||
v-if="settingsStore.areTagsEnabled"
|
||||
ref="dropdown"
|
||||
v-model="currentTagIds"
|
||||
:create-enabled="true"
|
||||
:event-bus="dropdownBus"
|
||||
:placeholder="$locale.baseText('duplicateWorkflowDialog.chooseOrCreateATag')"
|
||||
@blur="onTagsBlur"
|
||||
@esc="onTagsEsc"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer="{ close }">
|
||||
<div :class="$style.footer">
|
||||
<n8n-button
|
||||
:loading="isSaving"
|
||||
:label="$locale.baseText('duplicateWorkflowDialog.save')"
|
||||
float="right"
|
||||
@click="save"
|
||||
/>
|
||||
<n8n-button
|
||||
type="secondary"
|
||||
:disabled="isSaving"
|
||||
:label="$locale.baseText('duplicateWorkflowDialog.cancel')"
|
||||
float="right"
|
||||
@click="close"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
@@ -199,6 +150,55 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:event-bus="modalBus"
|
||||
:title="$locale.baseText('duplicateWorkflowDialog.duplicateWorkflow')"
|
||||
:center="true"
|
||||
width="420px"
|
||||
@enter="save"
|
||||
>
|
||||
<template #content>
|
||||
<div :class="$style.content">
|
||||
<n8n-input
|
||||
ref="nameInput"
|
||||
v-model="name"
|
||||
:placeholder="$locale.baseText('duplicateWorkflowDialog.enterWorkflowName')"
|
||||
:maxlength="MAX_WORKFLOW_NAME_LENGTH"
|
||||
/>
|
||||
<TagsDropdown
|
||||
v-if="settingsStore.areTagsEnabled"
|
||||
ref="dropdown"
|
||||
v-model="currentTagIds"
|
||||
:create-enabled="true"
|
||||
:event-bus="dropdownBus"
|
||||
:placeholder="$locale.baseText('duplicateWorkflowDialog.chooseOrCreateATag')"
|
||||
@blur="onTagsBlur"
|
||||
@esc="onTagsEsc"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer="{ close }">
|
||||
<div :class="$style.footer">
|
||||
<n8n-button
|
||||
:loading="isSaving"
|
||||
:label="$locale.baseText('duplicateWorkflowDialog.save')"
|
||||
float="right"
|
||||
@click="save"
|
||||
/>
|
||||
<n8n-button
|
||||
type="secondary"
|
||||
:disabled="isSaving"
|
||||
:label="$locale.baseText('duplicateWorkflowDialog.cancel')"
|
||||
float="right"
|
||||
@click="close"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.content {
|
||||
> *:not(:last-child) {
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot v-if="canAccess" />
|
||||
<slot v-else name="fallback" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { type PropType, defineComponent } from 'vue';
|
||||
import type { EnterpriseEditionFeatureValue } from '@/Interface';
|
||||
@@ -29,3 +22,10 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<slot v-if="canAccess" />
|
||||
<slot v-else name="fallback" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<!-- mock el-input element to apply styles -->
|
||||
<div :class="{ 'el-input': true, 'static-size': staticSize }" :data-value="hiddenValue">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
@@ -26,6 +19,13 @@ const hiddenValue = computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- mock el-input element to apply styles -->
|
||||
<div :class="{ 'el-input': true, 'static-size': staticSize }" :data-value="hiddenValue">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$--horiz-padding: 15px;
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
<template>
|
||||
<ExpandableInputBase :model-value="modelValue" :placeholder="placeholder">
|
||||
<input
|
||||
ref="inputRef"
|
||||
class="el-input__inner"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxlength"
|
||||
size="4"
|
||||
@input="onInput"
|
||||
@keydown.enter="onEnter"
|
||||
@keydown.esc="onEscape"
|
||||
/>
|
||||
</ExpandableInputBase>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EventBus } from 'n8n-design-system';
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
@@ -78,3 +62,19 @@ function onEscape() {
|
||||
emit('esc');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ExpandableInputBase :model-value="modelValue" :placeholder="placeholder">
|
||||
<input
|
||||
ref="inputRef"
|
||||
class="el-input__inner"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxlength"
|
||||
size="4"
|
||||
@input="onInput"
|
||||
@keydown.enter="onEnter"
|
||||
@keydown.esc="onEscape"
|
||||
/>
|
||||
</ExpandableInputBase>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import ExpandableInputBase from './ExpandableInputBase.vue';
|
||||
|
||||
type Props = {
|
||||
modelValue: string;
|
||||
};
|
||||
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ExpandableInputBase :model-value="modelValue" :static-size="true">
|
||||
<input
|
||||
@@ -9,16 +19,6 @@
|
||||
</ExpandableInputBase>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ExpandableInputBase from './ExpandableInputBase.vue';
|
||||
|
||||
type Props = {
|
||||
modelValue: string;
|
||||
};
|
||||
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
input,
|
||||
input:hover {
|
||||
|
||||
@@ -1,94 +1,3 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
width="calc(100vw - var(--spacing-3xl))"
|
||||
append-to-body
|
||||
:class="$style.modal"
|
||||
:model-value="dialogVisible"
|
||||
:before-close="closeDialog"
|
||||
>
|
||||
<button :class="$style.close" @click="closeDialog">
|
||||
<Close height="18" width="18" />
|
||||
</button>
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.sidebar">
|
||||
<N8nInput
|
||||
v-model="search"
|
||||
size="small"
|
||||
:class="$style.search"
|
||||
:placeholder="i18n.baseText('ndv.search.placeholder.input.schema')"
|
||||
>
|
||||
<template #prefix>
|
||||
<N8nIcon :class="$style.ioSearchIcon" icon="search" />
|
||||
</template>
|
||||
</N8nInput>
|
||||
|
||||
<RunDataSchema
|
||||
:class="$style.schema"
|
||||
:search="appliedSearch"
|
||||
:nodes="parentNodes"
|
||||
mapping-enabled
|
||||
pane-type="input"
|
||||
connection-type="main"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div :class="$style.io">
|
||||
<div :class="$style.input">
|
||||
<div :class="$style.header">
|
||||
<N8nText bold size="large">
|
||||
{{ i18n.baseText('expressionEdit.expression') }}
|
||||
</N8nText>
|
||||
<N8nText
|
||||
:class="$style.tip"
|
||||
size="small"
|
||||
v-html="i18n.baseText('expressionTip.javascript')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DraggableTarget :class="$style.editorContainer" type="mapping" @drop="onDrop">
|
||||
<template #default>
|
||||
<ExpressionEditorModalInput
|
||||
ref="expressionInputRef"
|
||||
:model-value="modelValue"
|
||||
:is-read-only="isReadOnly"
|
||||
:path="path"
|
||||
:class="[
|
||||
$style.editor,
|
||||
{
|
||||
'ph-no-capture': redactValues,
|
||||
},
|
||||
]"
|
||||
data-test-id="expression-modal-input"
|
||||
@change="valueChanged"
|
||||
@close="closeDialog"
|
||||
/>
|
||||
</template>
|
||||
</DraggableTarget>
|
||||
</div>
|
||||
|
||||
<div :class="$style.output">
|
||||
<div :class="$style.header">
|
||||
<N8nText bold size="large">
|
||||
{{ i18n.baseText('parameterInput.result') }}
|
||||
</N8nText>
|
||||
<OutputItemSelect />
|
||||
</div>
|
||||
|
||||
<div :class="[$style.editorContainer, { 'ph-no-capture': redactValues }]">
|
||||
<ExpressionOutput
|
||||
ref="expressionResultRef"
|
||||
:class="$style.editor"
|
||||
:segments="segments"
|
||||
:extensions="theme"
|
||||
data-test-id="expression-modal-output"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ExpressionEditorModalInput from '@/components/ExpressionEditorModal/ExpressionEditorModalInput.vue';
|
||||
import { computed, ref, toRaw, watch } from 'vue';
|
||||
@@ -211,6 +120,97 @@ async function onDrop(expression: string, event: MouseEvent) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
width="calc(100vw - var(--spacing-3xl))"
|
||||
append-to-body
|
||||
:class="$style.modal"
|
||||
:model-value="dialogVisible"
|
||||
:before-close="closeDialog"
|
||||
>
|
||||
<button :class="$style.close" @click="closeDialog">
|
||||
<Close height="18" width="18" />
|
||||
</button>
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.sidebar">
|
||||
<N8nInput
|
||||
v-model="search"
|
||||
size="small"
|
||||
:class="$style.search"
|
||||
:placeholder="i18n.baseText('ndv.search.placeholder.input.schema')"
|
||||
>
|
||||
<template #prefix>
|
||||
<N8nIcon :class="$style.ioSearchIcon" icon="search" />
|
||||
</template>
|
||||
</N8nInput>
|
||||
|
||||
<RunDataSchema
|
||||
:class="$style.schema"
|
||||
:search="appliedSearch"
|
||||
:nodes="parentNodes"
|
||||
mapping-enabled
|
||||
pane-type="input"
|
||||
connection-type="main"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div :class="$style.io">
|
||||
<div :class="$style.input">
|
||||
<div :class="$style.header">
|
||||
<N8nText bold size="large">
|
||||
{{ i18n.baseText('expressionEdit.expression') }}
|
||||
</N8nText>
|
||||
<N8nText
|
||||
:class="$style.tip"
|
||||
size="small"
|
||||
v-html="i18n.baseText('expressionTip.javascript')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DraggableTarget :class="$style.editorContainer" type="mapping" @drop="onDrop">
|
||||
<template #default>
|
||||
<ExpressionEditorModalInput
|
||||
ref="expressionInputRef"
|
||||
:model-value="modelValue"
|
||||
:is-read-only="isReadOnly"
|
||||
:path="path"
|
||||
:class="[
|
||||
$style.editor,
|
||||
{
|
||||
'ph-no-capture': redactValues,
|
||||
},
|
||||
]"
|
||||
data-test-id="expression-modal-input"
|
||||
@change="valueChanged"
|
||||
@close="closeDialog"
|
||||
/>
|
||||
</template>
|
||||
</DraggableTarget>
|
||||
</div>
|
||||
|
||||
<div :class="$style.output">
|
||||
<div :class="$style.header">
|
||||
<N8nText bold size="large">
|
||||
{{ i18n.baseText('parameterInput.result') }}
|
||||
</N8nText>
|
||||
<OutputItemSelect />
|
||||
</div>
|
||||
|
||||
<div :class="[$style.editorContainer, { 'ph-no-capture': redactValues }]">
|
||||
<ExpressionOutput
|
||||
ref="expressionResultRef"
|
||||
:class="$style.editor"
|
||||
:segments="segments"
|
||||
:extensions="theme"
|
||||
data-test-id="expression-modal-output"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.modal {
|
||||
--dialog-close-top: var(--spacing-m);
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
<template>
|
||||
<div ref="root" :class="$style.editor" @keydown.stop></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { history } from '@codemirror/commands';
|
||||
import { Prec } from '@codemirror/state';
|
||||
@@ -109,6 +105,10 @@ onMounted(() => {
|
||||
defineExpose({ editor });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="root" :class="$style.editor" @keydown.stop></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
:global(.cm-content) {
|
||||
border-radius: var(--border-radius-base);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ExpressionFunctionIcon',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
@@ -7,10 +13,4 @@
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ExpressionFunctionIcon',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -1,31 +1,3 @@
|
||||
<template>
|
||||
<div v-if="featureInfo" :class="[$style.container]">
|
||||
<div v-if="showTitle" class="mb-2xl">
|
||||
<n8n-heading size="2xlarge">
|
||||
{{ $locale.baseText(featureInfo.featureName) }}
|
||||
</n8n-heading>
|
||||
</div>
|
||||
<div v-if="featureInfo.infoText" class="mb-l">
|
||||
<n8n-info-tip theme="info" type="note">
|
||||
<span v-html="$locale.baseText(featureInfo.infoText)"></span>
|
||||
</n8n-info-tip>
|
||||
</div>
|
||||
<div :class="$style.actionBoxContainer">
|
||||
<n8n-action-box
|
||||
:description="$locale.baseText(featureInfo.actionBoxDescription)"
|
||||
:button-text="
|
||||
$locale.baseText(featureInfo.actionBoxButtonLabel || 'fakeDoor.actionBox.button.label')
|
||||
"
|
||||
@click:button="openLinkPage"
|
||||
>
|
||||
<template #heading>
|
||||
<span v-html="$locale.baseText(featureInfo.actionBoxTitle)" />
|
||||
</template>
|
||||
</n8n-action-box>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
@@ -75,6 +47,34 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="featureInfo" :class="[$style.container]">
|
||||
<div v-if="showTitle" class="mb-2xl">
|
||||
<n8n-heading size="2xlarge">
|
||||
{{ $locale.baseText(featureInfo.featureName) }}
|
||||
</n8n-heading>
|
||||
</div>
|
||||
<div v-if="featureInfo.infoText" class="mb-l">
|
||||
<n8n-info-tip theme="info" type="note">
|
||||
<span v-html="$locale.baseText(featureInfo.infoText)"></span>
|
||||
</n8n-info-tip>
|
||||
</div>
|
||||
<div :class="$style.actionBoxContainer">
|
||||
<n8n-action-box
|
||||
:description="$locale.baseText(featureInfo.actionBoxDescription)"
|
||||
:button-text="
|
||||
$locale.baseText(featureInfo.actionBoxButtonLabel || 'fakeDoor.actionBox.button.label')
|
||||
"
|
||||
@click:button="openLinkPage"
|
||||
>
|
||||
<template #heading>
|
||||
<span v-html="$locale.baseText(featureInfo.actionBoxTitle)" />
|
||||
</template>
|
||||
</n8n-action-box>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.actionBoxContainer {
|
||||
text-align: center;
|
||||
|
||||
@@ -1,131 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="fixed-collection-parameter"
|
||||
:data-test-id="`fixed-collection-${parameter.name}`"
|
||||
@keydown.stop
|
||||
>
|
||||
<div v-if="getProperties.length === 0" class="no-items-exist">
|
||||
<n8n-text size="small">{{
|
||||
$locale.baseText('fixedCollectionParameter.currentlyNoItemsExist')
|
||||
}}</n8n-text>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="property in getProperties"
|
||||
:key="property.name"
|
||||
class="fixed-collection-parameter-property"
|
||||
>
|
||||
<n8n-input-label
|
||||
v-if="property.displayName !== '' && parameter.options && parameter.options.length !== 1"
|
||||
:label="$locale.nodeText().inputLabelDisplayName(property, path)"
|
||||
:underline="true"
|
||||
size="small"
|
||||
color="text-dark"
|
||||
/>
|
||||
<div v-if="multipleValues">
|
||||
<div
|
||||
v-for="(_, index) in mutableValues[property.name]"
|
||||
:key="property.name + index"
|
||||
class="parameter-item"
|
||||
>
|
||||
<div
|
||||
:class="index ? 'border-top-dashed parameter-item-wrapper ' : 'parameter-item-wrapper'"
|
||||
>
|
||||
<div v-if="!isReadOnly" class="delete-option">
|
||||
<n8n-icon-button
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="trash"
|
||||
:title="$locale.baseText('fixedCollectionParameter.deleteItem')"
|
||||
@click="deleteOption(property.name, index)"
|
||||
></n8n-icon-button>
|
||||
<n8n-icon-button
|
||||
v-if="sortable && index !== 0"
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="angle-up"
|
||||
:title="$locale.baseText('fixedCollectionParameter.moveUp')"
|
||||
@click="moveOptionUp(property.name, index)"
|
||||
></n8n-icon-button>
|
||||
<n8n-icon-button
|
||||
v-if="sortable && index !== mutableValues[property.name].length - 1"
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="angle-down"
|
||||
:title="$locale.baseText('fixedCollectionParameter.moveDown')"
|
||||
@click="moveOptionDown(property.name, index)"
|
||||
></n8n-icon-button>
|
||||
</div>
|
||||
<Suspense>
|
||||
<ParameterInputList
|
||||
:parameters="property.values"
|
||||
:node-values="nodeValues"
|
||||
:path="getPropertyPath(property.name, index)"
|
||||
:hide-delete="true"
|
||||
:is-read-only="isReadOnly"
|
||||
@value-changed="valueChanged"
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="parameter-item">
|
||||
<div class="parameter-item-wrapper">
|
||||
<div v-if="!isReadOnly" class="delete-option">
|
||||
<n8n-icon-button
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="trash"
|
||||
:title="$locale.baseText('fixedCollectionParameter.deleteItem')"
|
||||
@click="deleteOption(property.name)"
|
||||
></n8n-icon-button>
|
||||
</div>
|
||||
<ParameterInputList
|
||||
:parameters="property.values"
|
||||
:node-values="nodeValues"
|
||||
:path="getPropertyPath(property.name)"
|
||||
:is-read-only="isReadOnly"
|
||||
class="parameter-item"
|
||||
:hide-delete="true"
|
||||
@value-changed="valueChanged"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="controls">
|
||||
<n8n-button
|
||||
v-if="parameter.options && parameter.options.length === 1"
|
||||
type="tertiary"
|
||||
block
|
||||
data-test-id="fixed-collection-add"
|
||||
:label="getPlaceholderText"
|
||||
@click="optionSelected(parameter.options[0].name)"
|
||||
/>
|
||||
<div v-else class="add-option">
|
||||
<n8n-select
|
||||
v-model="selectedOption"
|
||||
:placeholder="getPlaceholderText"
|
||||
size="small"
|
||||
filterable
|
||||
@update:model-value="optionSelected"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="item in parameterOptions"
|
||||
:key="item.name"
|
||||
:label="$locale.nodeText().collectionOptionDisplayName(parameter, item, path)"
|
||||
:value="item.name"
|
||||
></n8n-option>
|
||||
</n8n-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
@@ -339,6 +211,134 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="fixed-collection-parameter"
|
||||
:data-test-id="`fixed-collection-${parameter.name}`"
|
||||
@keydown.stop
|
||||
>
|
||||
<div v-if="getProperties.length === 0" class="no-items-exist">
|
||||
<n8n-text size="small">{{
|
||||
$locale.baseText('fixedCollectionParameter.currentlyNoItemsExist')
|
||||
}}</n8n-text>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="property in getProperties"
|
||||
:key="property.name"
|
||||
class="fixed-collection-parameter-property"
|
||||
>
|
||||
<n8n-input-label
|
||||
v-if="property.displayName !== '' && parameter.options && parameter.options.length !== 1"
|
||||
:label="$locale.nodeText().inputLabelDisplayName(property, path)"
|
||||
:underline="true"
|
||||
size="small"
|
||||
color="text-dark"
|
||||
/>
|
||||
<div v-if="multipleValues">
|
||||
<div
|
||||
v-for="(_, index) in mutableValues[property.name]"
|
||||
:key="property.name + index"
|
||||
class="parameter-item"
|
||||
>
|
||||
<div
|
||||
:class="index ? 'border-top-dashed parameter-item-wrapper ' : 'parameter-item-wrapper'"
|
||||
>
|
||||
<div v-if="!isReadOnly" class="delete-option">
|
||||
<n8n-icon-button
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="trash"
|
||||
:title="$locale.baseText('fixedCollectionParameter.deleteItem')"
|
||||
@click="deleteOption(property.name, index)"
|
||||
></n8n-icon-button>
|
||||
<n8n-icon-button
|
||||
v-if="sortable && index !== 0"
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="angle-up"
|
||||
:title="$locale.baseText('fixedCollectionParameter.moveUp')"
|
||||
@click="moveOptionUp(property.name, index)"
|
||||
></n8n-icon-button>
|
||||
<n8n-icon-button
|
||||
v-if="sortable && index !== mutableValues[property.name].length - 1"
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="angle-down"
|
||||
:title="$locale.baseText('fixedCollectionParameter.moveDown')"
|
||||
@click="moveOptionDown(property.name, index)"
|
||||
></n8n-icon-button>
|
||||
</div>
|
||||
<Suspense>
|
||||
<ParameterInputList
|
||||
:parameters="property.values"
|
||||
:node-values="nodeValues"
|
||||
:path="getPropertyPath(property.name, index)"
|
||||
:hide-delete="true"
|
||||
:is-read-only="isReadOnly"
|
||||
@value-changed="valueChanged"
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="parameter-item">
|
||||
<div class="parameter-item-wrapper">
|
||||
<div v-if="!isReadOnly" class="delete-option">
|
||||
<n8n-icon-button
|
||||
type="tertiary"
|
||||
text
|
||||
size="mini"
|
||||
icon="trash"
|
||||
:title="$locale.baseText('fixedCollectionParameter.deleteItem')"
|
||||
@click="deleteOption(property.name)"
|
||||
></n8n-icon-button>
|
||||
</div>
|
||||
<ParameterInputList
|
||||
:parameters="property.values"
|
||||
:node-values="nodeValues"
|
||||
:path="getPropertyPath(property.name)"
|
||||
:is-read-only="isReadOnly"
|
||||
class="parameter-item"
|
||||
:hide-delete="true"
|
||||
@value-changed="valueChanged"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="controls">
|
||||
<n8n-button
|
||||
v-if="parameter.options && parameter.options.length === 1"
|
||||
type="tertiary"
|
||||
block
|
||||
data-test-id="fixed-collection-add"
|
||||
:label="getPlaceholderText"
|
||||
@click="optionSelected(parameter.options[0].name)"
|
||||
/>
|
||||
<div v-else class="add-option">
|
||||
<n8n-select
|
||||
v-model="selectedOption"
|
||||
:placeholder="getPlaceholderText"
|
||||
size="small"
|
||||
filterable
|
||||
@update:model-value="optionSelected"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="item in parameterOptions"
|
||||
:key="item.name"
|
||||
:label="$locale.nodeText().collectionOptionDisplayName(parameter, item, path)"
|
||||
:value="item.name"
|
||||
></n8n-option>
|
||||
</n8n-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fixed-collection-parameter {
|
||||
padding-left: var(--spacing-s);
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.wrapper" @click="navigateTo">
|
||||
<font-awesome-icon :class="$style.icon" icon="arrow-left" />
|
||||
<div :class="$style.text" v-text="$locale.baseText('template.buttons.goBackButton')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import { VIEWS } from '@/constants';
|
||||
@@ -16,6 +9,13 @@ const navigateTo = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.wrapper" @click="navigateTo">
|
||||
<font-awesome-icon :class="$style.icon" icon="arrow-left" />
|
||||
<div :class="$style.text" v-text="$locale.baseText('template.buttons.goBackButton')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
|
||||
@@ -1,44 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
:class="$style.wrapper"
|
||||
:style="iconStyleData"
|
||||
@click="() => $emit('click')"
|
||||
@mouseover="showTooltip = true"
|
||||
@mouseleave="showTooltip = false"
|
||||
>
|
||||
<div :class="$style.tooltip">
|
||||
<n8n-tooltip placement="top" :visible="showTooltip">
|
||||
<template #content>
|
||||
<div v-text="nodeType.displayName"></div>
|
||||
</template>
|
||||
<span />
|
||||
</n8n-tooltip>
|
||||
</div>
|
||||
<div v-if="nodeIconData !== null" :class="$style.icon" title="">
|
||||
<div :class="$style.iconWrapper" :style="iconStyleData">
|
||||
<div v-if="nodeIconData !== null" :class="$style.icon">
|
||||
<img
|
||||
v-if="nodeIconData.type === 'file'"
|
||||
:src="nodeIconData.fileBuffer || nodeIconData.path"
|
||||
:style="imageStyleData"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
v-else
|
||||
:icon="nodeIconData.icon || nodeIconData.path"
|
||||
:style="fontStyleData"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="node-icon-placeholder">
|
||||
{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :class="$style.placeholder">
|
||||
{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { type StyleValue, defineComponent, type PropType } from 'vue';
|
||||
|
||||
@@ -151,6 +110,47 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="$style.wrapper"
|
||||
:style="iconStyleData"
|
||||
@click="() => $emit('click')"
|
||||
@mouseover="showTooltip = true"
|
||||
@mouseleave="showTooltip = false"
|
||||
>
|
||||
<div :class="$style.tooltip">
|
||||
<n8n-tooltip placement="top" :visible="showTooltip">
|
||||
<template #content>
|
||||
<div v-text="nodeType.displayName"></div>
|
||||
</template>
|
||||
<span />
|
||||
</n8n-tooltip>
|
||||
</div>
|
||||
<div v-if="nodeIconData !== null" :class="$style.icon" title="">
|
||||
<div :class="$style.iconWrapper" :style="iconStyleData">
|
||||
<div v-if="nodeIconData !== null" :class="$style.icon">
|
||||
<img
|
||||
v-if="nodeIconData.type === 'file'"
|
||||
:src="nodeIconData.fileBuffer || nodeIconData.path"
|
||||
:style="imageStyleData"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
v-else
|
||||
:icon="nodeIconData.icon || nodeIconData.path"
|
||||
:style="fontStyleData"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="node-icon-placeholder">
|
||||
{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :class="$style.placeholder">
|
||||
{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.wrapper {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.editor">
|
||||
<div ref="htmlEditor" data-test-id="html-editor-container"></div>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { history } from '@codemirror/commands';
|
||||
import {
|
||||
@@ -247,6 +240,13 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.editor">
|
||||
<div ref="htmlEditor" data-test-id="html-editor-container"></div>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.editor {
|
||||
height: 100%;
|
||||
|
||||
@@ -1,44 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
width="700px"
|
||||
:title="i18n.baseText('importCurlModal.title')"
|
||||
:event-bus="modalBus"
|
||||
:name="IMPORT_CURL_MODAL_KEY"
|
||||
:center="true"
|
||||
>
|
||||
<template #content>
|
||||
<div :class="$style.container">
|
||||
<N8nInputLabel :label="i18n.baseText('importCurlModal.input.label')" color="text-dark">
|
||||
<N8nInput
|
||||
ref="inputRef"
|
||||
:model-value="curlCommand"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
:placeholder="i18n.baseText('importCurlModal.input.placeholder')"
|
||||
@update:model-value="onInput"
|
||||
@focus="$event.target.select()"
|
||||
/>
|
||||
</N8nInputLabel>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.modalFooter">
|
||||
<N8nNotice
|
||||
:class="$style.notice"
|
||||
:content="i18n.baseText('ImportCurlModal.notice.content')"
|
||||
/>
|
||||
<div>
|
||||
<N8nButton
|
||||
float="right"
|
||||
:label="i18n.baseText('importCurlModal.button.label')"
|
||||
@click="onImport"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Modal from '@/components/Modal.vue';
|
||||
import { IMPORT_CURL_MODAL_KEY } from '@/constants';
|
||||
@@ -116,6 +75,47 @@ async function onImport() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
width="700px"
|
||||
:title="i18n.baseText('importCurlModal.title')"
|
||||
:event-bus="modalBus"
|
||||
:name="IMPORT_CURL_MODAL_KEY"
|
||||
:center="true"
|
||||
>
|
||||
<template #content>
|
||||
<div :class="$style.container">
|
||||
<N8nInputLabel :label="i18n.baseText('importCurlModal.input.label')" color="text-dark">
|
||||
<N8nInput
|
||||
ref="inputRef"
|
||||
:model-value="curlCommand"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
:placeholder="i18n.baseText('importCurlModal.input.placeholder')"
|
||||
@update:model-value="onInput"
|
||||
@focus="$event.target.select()"
|
||||
/>
|
||||
</N8nInputLabel>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.modalFooter">
|
||||
<N8nNotice
|
||||
:class="$style.notice"
|
||||
:content="i18n.baseText('ImportCurlModal.notice.content')"
|
||||
/>
|
||||
<div>
|
||||
<N8nButton
|
||||
float="right"
|
||||
:label="i18n.baseText('importCurlModal.button.label')"
|
||||
@click="onImport"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.modalFooter {
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -1,35 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<span v-if="readonly" :class="$style.headline">
|
||||
{{ modelValue }}
|
||||
</span>
|
||||
<div
|
||||
v-else
|
||||
:class="[$style.headline, $style['headline-editable']]"
|
||||
@keydown.stop
|
||||
@click="enableNameEdit"
|
||||
>
|
||||
<div v-if="!isNameEdit">
|
||||
<span>{{ modelValue }}</span>
|
||||
<i><font-awesome-icon icon="pen" /></i>
|
||||
</div>
|
||||
<div v-else :class="$style.nameInput">
|
||||
<n8n-input
|
||||
ref="nameInput"
|
||||
:model-value="modelValue"
|
||||
size="xlarge"
|
||||
:maxlength="64"
|
||||
@update:model-value="onNameEdit"
|
||||
@change="disableNameEdit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isNameEdit && subtitle" :class="$style.subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
@@ -79,6 +47,38 @@ const disableNameEdit = () => {
|
||||
onClickOutside(nameInput, disableNameEdit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<span v-if="readonly" :class="$style.headline">
|
||||
{{ modelValue }}
|
||||
</span>
|
||||
<div
|
||||
v-else
|
||||
:class="[$style.headline, $style['headline-editable']]"
|
||||
@keydown.stop
|
||||
@click="enableNameEdit"
|
||||
>
|
||||
<div v-if="!isNameEdit">
|
||||
<span>{{ modelValue }}</span>
|
||||
<i><font-awesome-icon icon="pen" /></i>
|
||||
</div>
|
||||
<div v-else :class="$style.nameInput">
|
||||
<n8n-input
|
||||
ref="nameInput"
|
||||
:model-value="modelValue"
|
||||
size="xlarge"
|
||||
:maxlength="64"
|
||||
@update:model-value="onNameEdit"
|
||||
@change="disableNameEdit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isNameEdit && subtitle" :class="$style.subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
<template>
|
||||
<span class="inline-edit" @keydown.stop>
|
||||
<span v-if="isEditEnabled && !isDisabled">
|
||||
<ExpandableInputEdit
|
||||
v-model="newValue"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxLength"
|
||||
:autofocus="true"
|
||||
:event-bus="inputBus"
|
||||
@update:model-value="onInput"
|
||||
@esc="onEscape"
|
||||
@blur="onBlur"
|
||||
@enter="submit"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span v-else class="preview" @click="onClick">
|
||||
<ExpandableInputPreview :model-value="previewValue || modelValue" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import ExpandableInputEdit from '@/components/ExpandableInput/ExpandableInputEdit.vue';
|
||||
@@ -100,6 +78,28 @@ function onEscape() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="inline-edit" @keydown.stop>
|
||||
<span v-if="isEditEnabled && !isDisabled">
|
||||
<ExpandableInputEdit
|
||||
v-model="newValue"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxLength"
|
||||
:autofocus="true"
|
||||
:event-bus="inputBus"
|
||||
@update:model-value="onInput"
|
||||
@esc="onEscape"
|
||||
@blur="onBlur"
|
||||
@enter="submit"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span v-else class="preview" @click="onClick">
|
||||
<ExpandableInputPreview :model-value="previewValue || modelValue" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.preview {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,138 +1,3 @@
|
||||
<template>
|
||||
<RunData
|
||||
:node="currentNode"
|
||||
:nodes="isMappingMode ? rootNodesParents : parentNodes"
|
||||
:workflow="workflow"
|
||||
:run-index="runIndex"
|
||||
:linked-runs="linkedRuns"
|
||||
:can-link-runs="!mappedNode && canLinkRuns"
|
||||
:too-much-data-title="$locale.baseText('ndv.input.tooMuchData.title')"
|
||||
:no-data-in-branch-message="$locale.baseText('ndv.input.noOutputDataInBranch')"
|
||||
:is-executing="isExecutingPrevious"
|
||||
:executing-message="$locale.baseText('ndv.input.executingPrevious')"
|
||||
:push-ref="pushRef"
|
||||
:override-outputs="connectedCurrentNodeOutputs"
|
||||
:mapping-enabled="isMappingEnabled"
|
||||
:distance-from-active="currentNodeDepth"
|
||||
:is-production-execution-preview="isProductionExecutionPreview"
|
||||
:is-pane-active="isPaneActive"
|
||||
pane-type="input"
|
||||
data-test-id="ndv-input-panel"
|
||||
@activate-pane="activatePane"
|
||||
@item-hover="$emit('itemHover', $event)"
|
||||
@link-run="onLinkRun"
|
||||
@unlink-run="onUnlinkRun"
|
||||
@run-change="onRunIndexChange"
|
||||
@table-mounted="$emit('tableMounted', $event)"
|
||||
@search="$emit('search', $event)"
|
||||
>
|
||||
<template #header>
|
||||
<div :class="$style.titleSection">
|
||||
<span :class="$style.title">{{ $locale.baseText('ndv.input') }}</span>
|
||||
<n8n-radio-buttons
|
||||
v-if="isActiveNodeConfig && !readOnly"
|
||||
:options="inputModes"
|
||||
:model-value="inputMode"
|
||||
@update:model-value="onInputModeChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #input-select>
|
||||
<InputNodeSelect
|
||||
v-if="parentNodes.length && currentNodeName"
|
||||
:model-value="currentNodeName"
|
||||
:workflow="workflow"
|
||||
:nodes="parentNodes"
|
||||
@update:model-value="onInputNodeChange"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="isMappingMode" #before-data>
|
||||
<!--
|
||||
Hide the run linking buttons for both input and ouput panels when in 'Mapping Mode' because the run indices wouldn't match.
|
||||
Although this is not the most elegant solution, it's straightforward and simpler than introducing a new props and logic to handle this.
|
||||
-->
|
||||
<component :is="'style'">button.linkRun { display: none }</component>
|
||||
<div :class="$style.mappedNode">
|
||||
<InputNodeSelect
|
||||
:model-value="mappedNode"
|
||||
:workflow="workflow"
|
||||
:nodes="rootNodesParents"
|
||||
@update:model-value="onMappedNodeSelected"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #node-not-run>
|
||||
<div
|
||||
v-if="(isActiveNodeConfig && rootNode) || parentNodes.length"
|
||||
:class="$style.noOutputData"
|
||||
>
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('ndv.input.noOutputData.title')
|
||||
}}</n8n-text>
|
||||
<n8n-tooltip v-if="!readOnly" :visible="showDraggableHint && showDraggableHintWithDelay">
|
||||
<template #content>
|
||||
<div
|
||||
v-html="
|
||||
$locale.baseText('dataMapping.dragFromPreviousHint', {
|
||||
interpolate: { name: focusedMappableInput },
|
||||
})
|
||||
"
|
||||
></div>
|
||||
</template>
|
||||
<NodeExecuteButton
|
||||
type="secondary"
|
||||
hide-icon
|
||||
:transparent="true"
|
||||
:node-name="isActiveNodeConfig ? rootNode : currentNodeName ?? ''"
|
||||
:label="$locale.baseText('ndv.input.noOutputData.executePrevious')"
|
||||
telemetry-source="inputs"
|
||||
data-test-id="execute-previous-node"
|
||||
@execute="onNodeExecute"
|
||||
/>
|
||||
</n8n-tooltip>
|
||||
<n8n-text v-if="!readOnly" tag="div" size="small">
|
||||
{{ $locale.baseText('ndv.input.noOutputData.hint') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
<div v-else :class="$style.notConnected">
|
||||
<div>
|
||||
<WireMeUp />
|
||||
</div>
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('ndv.input.notConnected.title')
|
||||
}}</n8n-text>
|
||||
<n8n-text tag="div">
|
||||
{{ $locale.baseText('ndv.input.notConnected.message') }}
|
||||
<a
|
||||
href="https://docs.n8n.io/workflows/connections/"
|
||||
target="_blank"
|
||||
@click="onConnectionHelpClick"
|
||||
>
|
||||
{{ $locale.baseText('ndv.input.notConnected.learnMore') }}
|
||||
</a>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #no-output-data>
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('ndv.input.noOutputData')
|
||||
}}</n8n-text>
|
||||
</template>
|
||||
|
||||
<template #recovered-artificial-output-data>
|
||||
<div :class="$style.recoveredOutputData">
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('executionDetails.executionFailed.recoveredNodeTitle')
|
||||
}}</n8n-text>
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('executionDetails.executionFailed.recoveredNodeMessage') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
</RunData>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import {
|
||||
@@ -468,6 +333,141 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RunData
|
||||
:node="currentNode"
|
||||
:nodes="isMappingMode ? rootNodesParents : parentNodes"
|
||||
:workflow="workflow"
|
||||
:run-index="runIndex"
|
||||
:linked-runs="linkedRuns"
|
||||
:can-link-runs="!mappedNode && canLinkRuns"
|
||||
:too-much-data-title="$locale.baseText('ndv.input.tooMuchData.title')"
|
||||
:no-data-in-branch-message="$locale.baseText('ndv.input.noOutputDataInBranch')"
|
||||
:is-executing="isExecutingPrevious"
|
||||
:executing-message="$locale.baseText('ndv.input.executingPrevious')"
|
||||
:push-ref="pushRef"
|
||||
:override-outputs="connectedCurrentNodeOutputs"
|
||||
:mapping-enabled="isMappingEnabled"
|
||||
:distance-from-active="currentNodeDepth"
|
||||
:is-production-execution-preview="isProductionExecutionPreview"
|
||||
:is-pane-active="isPaneActive"
|
||||
pane-type="input"
|
||||
data-test-id="ndv-input-panel"
|
||||
@activate-pane="activatePane"
|
||||
@item-hover="$emit('itemHover', $event)"
|
||||
@link-run="onLinkRun"
|
||||
@unlink-run="onUnlinkRun"
|
||||
@run-change="onRunIndexChange"
|
||||
@table-mounted="$emit('tableMounted', $event)"
|
||||
@search="$emit('search', $event)"
|
||||
>
|
||||
<template #header>
|
||||
<div :class="$style.titleSection">
|
||||
<span :class="$style.title">{{ $locale.baseText('ndv.input') }}</span>
|
||||
<n8n-radio-buttons
|
||||
v-if="isActiveNodeConfig && !readOnly"
|
||||
:options="inputModes"
|
||||
:model-value="inputMode"
|
||||
@update:model-value="onInputModeChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #input-select>
|
||||
<InputNodeSelect
|
||||
v-if="parentNodes.length && currentNodeName"
|
||||
:model-value="currentNodeName"
|
||||
:workflow="workflow"
|
||||
:nodes="parentNodes"
|
||||
@update:model-value="onInputNodeChange"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="isMappingMode" #before-data>
|
||||
<!--
|
||||
Hide the run linking buttons for both input and ouput panels when in 'Mapping Mode' because the run indices wouldn't match.
|
||||
Although this is not the most elegant solution, it's straightforward and simpler than introducing a new props and logic to handle this.
|
||||
-->
|
||||
<component :is="'style'">button.linkRun { display: none }</component>
|
||||
<div :class="$style.mappedNode">
|
||||
<InputNodeSelect
|
||||
:model-value="mappedNode"
|
||||
:workflow="workflow"
|
||||
:nodes="rootNodesParents"
|
||||
@update:model-value="onMappedNodeSelected"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #node-not-run>
|
||||
<div
|
||||
v-if="(isActiveNodeConfig && rootNode) || parentNodes.length"
|
||||
:class="$style.noOutputData"
|
||||
>
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('ndv.input.noOutputData.title')
|
||||
}}</n8n-text>
|
||||
<n8n-tooltip v-if="!readOnly" :visible="showDraggableHint && showDraggableHintWithDelay">
|
||||
<template #content>
|
||||
<div
|
||||
v-html="
|
||||
$locale.baseText('dataMapping.dragFromPreviousHint', {
|
||||
interpolate: { name: focusedMappableInput },
|
||||
})
|
||||
"
|
||||
></div>
|
||||
</template>
|
||||
<NodeExecuteButton
|
||||
type="secondary"
|
||||
hide-icon
|
||||
:transparent="true"
|
||||
:node-name="isActiveNodeConfig ? rootNode : currentNodeName ?? ''"
|
||||
:label="$locale.baseText('ndv.input.noOutputData.executePrevious')"
|
||||
telemetry-source="inputs"
|
||||
data-test-id="execute-previous-node"
|
||||
@execute="onNodeExecute"
|
||||
/>
|
||||
</n8n-tooltip>
|
||||
<n8n-text v-if="!readOnly" tag="div" size="small">
|
||||
{{ $locale.baseText('ndv.input.noOutputData.hint') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
<div v-else :class="$style.notConnected">
|
||||
<div>
|
||||
<WireMeUp />
|
||||
</div>
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('ndv.input.notConnected.title')
|
||||
}}</n8n-text>
|
||||
<n8n-text tag="div">
|
||||
{{ $locale.baseText('ndv.input.notConnected.message') }}
|
||||
<a
|
||||
href="https://docs.n8n.io/workflows/connections/"
|
||||
target="_blank"
|
||||
@click="onConnectionHelpClick"
|
||||
>
|
||||
{{ $locale.baseText('ndv.input.notConnected.learnMore') }}
|
||||
</a>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #no-output-data>
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('ndv.input.noOutputData')
|
||||
}}</n8n-text>
|
||||
</template>
|
||||
|
||||
<template #recovered-artificial-output-data>
|
||||
<div :class="$style.recoveredOutputData">
|
||||
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
$locale.baseText('executionDetails.executionFailed.recoveredNodeTitle')
|
||||
}}</n8n-text>
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('executionDetails.executionFailed.recoveredNodeMessage') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
</RunData>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.mappedNode {
|
||||
padding: 0 var(--spacing-s) var(--spacing-s);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<span ref="observed">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
@@ -37,3 +31,9 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span ref="observed">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<div ref="root">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
@@ -70,3 +64,9 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="root">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,65 +1,3 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="INVITE_USER_MODAL_KEY"
|
||||
:title="
|
||||
$locale.baseText(
|
||||
showInviteUrls ? 'settings.users.copyInviteUrls' : 'settings.users.inviteNewUsers',
|
||||
)
|
||||
"
|
||||
:center="true"
|
||||
width="460px"
|
||||
:event-bus="modalBus"
|
||||
@enter="onSubmit"
|
||||
>
|
||||
<template #content>
|
||||
<n8n-notice v-if="!isAdvancedPermissionsEnabled">
|
||||
<i18n-t keypath="settings.users.advancedPermissions.warning">
|
||||
<template #link>
|
||||
<n8n-link size="small" @click="goToUpgradeAdvancedPermissions">
|
||||
{{ $locale.baseText('settings.users.advancedPermissions.warning.link') }}
|
||||
</n8n-link>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</n8n-notice>
|
||||
<div v-if="showInviteUrls">
|
||||
<n8n-users-list :users="invitedUsers">
|
||||
<template #actions="{ user }">
|
||||
<n8n-tooltip>
|
||||
<template #content>
|
||||
{{ $locale.baseText('settings.users.inviteLink.copy') }}
|
||||
</template>
|
||||
<n8n-icon-button
|
||||
icon="link"
|
||||
type="tertiary"
|
||||
data-test-id="copy-invite-link-button"
|
||||
:data-invite-link="user.inviteAcceptUrl"
|
||||
@click="onCopyInviteLink(user)"
|
||||
></n8n-icon-button>
|
||||
</n8n-tooltip>
|
||||
</template>
|
||||
</n8n-users-list>
|
||||
</div>
|
||||
<n8n-form-inputs
|
||||
v-else
|
||||
:inputs="config"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
@update="onInput"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="!showInviteUrls" #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:disabled="!enabledButton"
|
||||
:label="buttonLabel"
|
||||
float="right"
|
||||
@click="onSubmitClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
@@ -344,3 +282,65 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:name="INVITE_USER_MODAL_KEY"
|
||||
:title="
|
||||
$locale.baseText(
|
||||
showInviteUrls ? 'settings.users.copyInviteUrls' : 'settings.users.inviteNewUsers',
|
||||
)
|
||||
"
|
||||
:center="true"
|
||||
width="460px"
|
||||
:event-bus="modalBus"
|
||||
@enter="onSubmit"
|
||||
>
|
||||
<template #content>
|
||||
<n8n-notice v-if="!isAdvancedPermissionsEnabled">
|
||||
<i18n-t keypath="settings.users.advancedPermissions.warning">
|
||||
<template #link>
|
||||
<n8n-link size="small" @click="goToUpgradeAdvancedPermissions">
|
||||
{{ $locale.baseText('settings.users.advancedPermissions.warning.link') }}
|
||||
</n8n-link>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</n8n-notice>
|
||||
<div v-if="showInviteUrls">
|
||||
<n8n-users-list :users="invitedUsers">
|
||||
<template #actions="{ user }">
|
||||
<n8n-tooltip>
|
||||
<template #content>
|
||||
{{ $locale.baseText('settings.users.inviteLink.copy') }}
|
||||
</template>
|
||||
<n8n-icon-button
|
||||
icon="link"
|
||||
type="tertiary"
|
||||
data-test-id="copy-invite-link-button"
|
||||
:data-invite-link="user.inviteAcceptUrl"
|
||||
@click="onCopyInviteLink(user)"
|
||||
></n8n-icon-button>
|
||||
</n8n-tooltip>
|
||||
</template>
|
||||
</n8n-users-list>
|
||||
</div>
|
||||
<n8n-form-inputs
|
||||
v-else
|
||||
:inputs="config"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
@update="onInput"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="!showInviteUrls" #footer>
|
||||
<n8n-button
|
||||
:loading="loading"
|
||||
:disabled="!enabledButton"
|
||||
:label="buttonLabel"
|
||||
float="right"
|
||||
@click="onSubmitClick"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.editor" :style="isReadOnly ? 'opacity: 0.7' : ''">
|
||||
<div ref="jsEditorRef" class="ph-no-capture js-editor"></div>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { history, toggleComment } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
@@ -124,6 +117,13 @@ const extensions = computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.editor" :style="isReadOnly ? 'opacity: 0.7' : ''">
|
||||
<div ref="jsEditorRef" class="ph-no-capture js-editor"></div>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.editor {
|
||||
height: 100%;
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<div :class="$style.editor">
|
||||
<div ref="jsonEditorRef" class="ph-no-capture json-editor"></div>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { history } from '@codemirror/commands';
|
||||
import { json, jsonParseLinter } from '@codemirror/lang-json';
|
||||
@@ -115,6 +108,13 @@ function destroyEditor() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.editor">
|
||||
<div ref="jsonEditorRef" class="ph-no-capture json-editor"></div>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.editor {
|
||||
height: 100%;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user