mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
✨ Add new version notification (#1977)
* add menu item
* implement versions modal
* fix up modal
* clean up badges
* implement key features
* fix up spacing
* add error message
* add notification icon
* fix notification
* fix bug when no updates
* address lint issues
* address multi line nodes
* add closing animation
* keep drawer open
* address design feedback
* address badge styling
* use grid for icons
* update cli to return version information
* set env variables
* add scss color variables
* address comments
* fix lint issue
* handle edge cases
* update scss variables, spacing
* update spacing
* build
* override top value for theme
* bolden version
* update config
* check endpoint exists
* handle long names
* set dates
* set title
* fix bug
* update warning
* remove unused component
* refactor components
* add fragments
* inherit styles
* fix icon size
* fix lint issues
* add cli dep
* address comments
* handle network error
* address empty case
* Revert "address comments"
480f969e07
* remove dependency
* build
* update variable names
* update variable names
* refactor verion card
* split out variables
* clean up impl
* clean up scss
* move from nodeview
* refactor out gift notification icon
* fix lint issues
* clean up variables
* update scss variables
* update info url
* Add instanceId to frontendSettings
* Use createHash from crypto module
* Add instanceId to store & send it as http header
* Fix lintings
* Fix interfaces & apply review changes
* Apply review changes
* add console message
* update text info
* update endpoint
* clean up interface
* address comments
* cleanup todo
* Update packages/cli/config/index.ts
Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>
* update console message
* ⚡ Display node-name on hover
* ⚡ Formatting fix
Co-authored-by: MedAliMarz <servfrdali@yahoo.fr>
Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
073e5e24cb
commit
98ec23544b
|
@ -142,6 +142,9 @@ export class Start extends Command {
|
|||
LoggerProxy.init(logger);
|
||||
logger.info('Initializing n8n process');
|
||||
|
||||
// todo remove a few versions after release
|
||||
logger.info('\nn8n now checks for new versions and security updates. You can turn this off using the environment variable N8N_VERSION_NOTIFICATIONS_ENABLED to "false"\nFor more information, please refer to https://docs.n8n.io/getting-started/installation/advanced/configuration.html\n');
|
||||
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init().catch((error: Error) => {
|
||||
logger.error(`There was an error initializing DB: "${error.message}"`);
|
||||
|
|
|
@ -618,6 +618,27 @@ const config = convict({
|
|||
},
|
||||
},
|
||||
|
||||
versionNotifications: {
|
||||
enabled: {
|
||||
doc: 'Whether feature is enabled to request notifications about new versions and security updates.',
|
||||
format: Boolean,
|
||||
default: true,
|
||||
env: 'N8N_VERSION_NOTIFICATIONS_ENABLED',
|
||||
},
|
||||
endpoint: {
|
||||
doc: 'Endpoint to retrieve version information from.',
|
||||
format: String,
|
||||
default: 'https://api.n8n.io/versions/',
|
||||
env: 'N8N_VERSION_NOTIFICATIONS_ENDPOINT',
|
||||
},
|
||||
infoUrl: {
|
||||
doc: `Url in New Versions Panel with more information on updating one's instance.`,
|
||||
format: String,
|
||||
default: 'https://docs.n8n.io/getting-started/installation/updating.html',
|
||||
env: 'N8N_VERSION_NOTIFICATIONS_INFO_URL',
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// Overwrite default configuration with settings which got defined in
|
||||
|
|
|
@ -312,6 +312,11 @@ export interface IN8nConfigNodes {
|
|||
exclude: string[];
|
||||
}
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
infoUrl: string;
|
||||
}
|
||||
|
||||
export interface IN8nUISettings {
|
||||
endpointWebhook: string;
|
||||
|
@ -331,6 +336,8 @@ export interface IN8nUISettings {
|
|||
n8nMetadata?: {
|
||||
[key: string]: string | number | undefined;
|
||||
};
|
||||
versionNotifications: IVersionNotificationSettings;
|
||||
instanceId: string;
|
||||
}
|
||||
|
||||
export interface IPackageVersions {
|
||||
|
|
|
@ -21,7 +21,7 @@ import * as clientOAuth1 from 'oauth-1.0a';
|
|||
import { RequestOptions } from 'oauth-1.0a';
|
||||
import * as csrf from 'csrf';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
import { createHmac } from 'crypto';
|
||||
import { createHash, createHmac } from 'crypto';
|
||||
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||
import { compare } from 'bcryptjs';
|
||||
|
@ -196,6 +196,12 @@ class App {
|
|||
'oauth1': urlBaseWebhook + `${this.restEndpoint}/oauth1-credential/callback`,
|
||||
'oauth2': urlBaseWebhook + `${this.restEndpoint}/oauth2-credential/callback`,
|
||||
},
|
||||
versionNotifications: {
|
||||
enabled: config.get('versionNotifications.enabled'),
|
||||
endpoint: config.get('versionNotifications.endpoint'),
|
||||
infoUrl: config.get('versionNotifications.infoUrl'),
|
||||
},
|
||||
instanceId: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -225,6 +231,7 @@ class App {
|
|||
|
||||
this.versions = await GenericHelpers.getVersions();
|
||||
this.frontendSettings.versionCli = this.versions.cli;
|
||||
this.frontendSettings.instanceId = await generateInstanceId() as string;
|
||||
|
||||
await this.externalHooks.run('frontend.settings', [this.frontendSettings]);
|
||||
|
||||
|
@ -2210,3 +2217,10 @@ async function getExecutionsCount(countFilter: IDataObject): Promise<{ count: nu
|
|||
const count = await Db.collections.Execution!.count(countFilter);
|
||||
return { count, estimate: false };
|
||||
}
|
||||
|
||||
async function generateInstanceId() {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
const hash = encryptionKey ? createHash('sha256').update(encryptionKey.slice(Math.round(encryptionKey.length / 2))).digest('hex') : undefined;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"v-click-outside": "^3.1.2"
|
||||
"timeago.js": "^4.0.2",
|
||||
"v-click-outside": "^3.1.2",
|
||||
"vue-fragment": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@beyonk/google-fonts-webpack-plugin": "^1.5.0",
|
||||
|
|
|
@ -445,6 +445,12 @@ export interface IPushDataConsoleMessage {
|
|||
message: string;
|
||||
}
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
infoUrl: string;
|
||||
}
|
||||
|
||||
export interface IN8nUISettings {
|
||||
endpointWebhook: string;
|
||||
endpointWebhookTest: string;
|
||||
|
@ -463,6 +469,8 @@ export interface IN8nUISettings {
|
|||
n8nMetadata?: {
|
||||
[key: string]: string | number | undefined;
|
||||
};
|
||||
versionNotifications: IVersionNotificationSettings;
|
||||
instanceId: string;
|
||||
}
|
||||
|
||||
export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||
|
@ -547,6 +555,29 @@ export interface ITagRow {
|
|||
delete?: boolean;
|
||||
}
|
||||
|
||||
export interface IVersion {
|
||||
name: string;
|
||||
nodes: IVersionNode[];
|
||||
createdAt: string;
|
||||
description: string;
|
||||
documentationUrl: string;
|
||||
hasBreakingChange: boolean;
|
||||
hasSecurityFix: boolean;
|
||||
hasSecurityIssue: boolean;
|
||||
securityIssueFixVersion: string;
|
||||
}
|
||||
|
||||
export interface IVersionNode {
|
||||
name: string;
|
||||
displayName: string;
|
||||
icon: string;
|
||||
defaults: INodeParameters;
|
||||
iconData: {
|
||||
type: string;
|
||||
icon?: string;
|
||||
fileBuffer?: string;
|
||||
};
|
||||
}
|
||||
export interface IRootState {
|
||||
activeExecutions: IExecutionsCurrentSummaryExtended[];
|
||||
activeWorkflows: string[];
|
||||
|
@ -583,6 +614,7 @@ export interface IRootState {
|
|||
urlBaseWebhook: string;
|
||||
workflow: IWorkflowDb;
|
||||
sidebarMenuItems: IMenuItem[];
|
||||
instanceId: string;
|
||||
}
|
||||
|
||||
export interface ITagsState {
|
||||
|
@ -605,6 +637,12 @@ export interface IUiState {
|
|||
isPageLoading: boolean;
|
||||
}
|
||||
|
||||
export interface IVersionsState {
|
||||
versionNotificationSettings: IVersionNotificationSettings;
|
||||
nextVersions: IVersion[];
|
||||
currentVersion: IVersion | undefined;
|
||||
}
|
||||
|
||||
export interface IWorkflowsState {
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
IRestApiContext,
|
||||
} from '../Interface';
|
||||
|
||||
|
||||
class ResponseError extends Error {
|
||||
// The HTTP status code of response
|
||||
httpStatusCode?: number;
|
||||
|
@ -91,6 +90,6 @@ export async function makeRestApiRequest(context: IRestApiContext, method: Metho
|
|||
return response.data;
|
||||
}
|
||||
|
||||
export async function get(baseURL: string, endpoint: string, params?: IDataObject) {
|
||||
return await request({method: 'GET', baseURL, endpoint, data: params});
|
||||
export async function get(baseURL: string, endpoint: string, params?: IDataObject, headers?: IDataObject) {
|
||||
return await request({method: 'GET', baseURL, endpoint, headers, data: params});
|
||||
}
|
||||
|
|
9
packages/editor-ui/src/api/versions.ts
Normal file
9
packages/editor-ui/src/api/versions.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { IVersion } from '@/Interface';
|
||||
import { INSTANCE_ID_HEADER } from '@/constants';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
import { get } from './helpers';
|
||||
|
||||
export async function getNextVersions(endpoint: string, version: string, instanceId: string): Promise<IVersion[]> {
|
||||
const headers = {[INSTANCE_ID_HEADER as string] : instanceId};
|
||||
return await get(endpoint, version, {}, headers);
|
||||
}
|
51
packages/editor-ui/src/components/Badge.vue
Normal file
51
packages/editor-ui/src/components/Badge.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template functional>
|
||||
<fragment>
|
||||
<el-tag
|
||||
v-if="props.type === 'danger'"
|
||||
type="danger"
|
||||
size="small"
|
||||
:class="$style['danger']"
|
||||
>
|
||||
{{ props.text }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="props.type === 'warning'"
|
||||
size="small"
|
||||
:class="$style['warning']"
|
||||
>
|
||||
{{ props.text }}
|
||||
</el-tag>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ["text", "type"],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
max-height: 18px;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.danger {
|
||||
composes: badge;
|
||||
color: $--badge-danger-color;
|
||||
background-color: $--badge-danger-background-color;
|
||||
border-color: $--badge-danger-border-color;
|
||||
}
|
||||
|
||||
.warning {
|
||||
composes: badge;
|
||||
background-color: $--badge-warning-background-color;
|
||||
color: $--badge-warning-color;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
36
packages/editor-ui/src/components/GiftNotificationIcon.vue
Normal file
36
packages/editor-ui/src/components/GiftNotificationIcon.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div :class="$style['gift-icon']">
|
||||
<font-awesome-icon icon="gift" />
|
||||
<div :class="$style['notification']">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.gift-icon {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.notification {
|
||||
height: .47em;
|
||||
width: .47em;
|
||||
border-radius: 50%;
|
||||
color: $--gift-notification-active-color;
|
||||
position: absolute;
|
||||
background-color: $--gift-notification-outer-color;
|
||||
right: -.3em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: -.148em;
|
||||
|
||||
div {
|
||||
height: .36em;
|
||||
width: .36em;
|
||||
background-color: $--gift-notification-inner-color;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -127,6 +127,14 @@
|
|||
|
||||
<MenuItemsIterator :items="sidebarMenuBottomItems" :root="true"/>
|
||||
|
||||
<div class="footer-menu-items">
|
||||
<el-menu-item index="updates" class="updates" v-if="hasVersionUpdates" @click="openUpdatesPanel">
|
||||
<div class="gift-container">
|
||||
<GiftNotificationIcon />
|
||||
</div>
|
||||
<span slot="title" class="item-title-root">{{nextVersions.length > 99 ? '99+' : nextVersions.length}} update{{nextVersions.length > 1 ? 's' : ''}} available</span>
|
||||
</el-menu-item>
|
||||
</div>
|
||||
</el-menu>
|
||||
|
||||
</div>
|
||||
|
@ -149,6 +157,7 @@ import About from '@/components/About.vue';
|
|||
import CredentialsEdit from '@/components/CredentialsEdit.vue';
|
||||
import CredentialsList from '@/components/CredentialsList.vue';
|
||||
import ExecutionsList from '@/components/ExecutionsList.vue';
|
||||
import GiftNotificationIcon from './GiftNotificationIcon.vue';
|
||||
import WorkflowSettings from '@/components/WorkflowSettings.vue';
|
||||
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
|
@ -212,6 +221,7 @@ export default mixins(
|
|||
CredentialsEdit,
|
||||
CredentialsList,
|
||||
ExecutionsList,
|
||||
GiftNotificationIcon,
|
||||
WorkflowSettings,
|
||||
MenuItemsIterator,
|
||||
},
|
||||
|
@ -232,6 +242,10 @@ export default mixins(
|
|||
...mapGetters('ui', {
|
||||
isCollapsed: 'sidebarMenuCollapsed',
|
||||
}),
|
||||
...mapGetters('versions', [
|
||||
'hasVersionUpdates',
|
||||
'nextVersions',
|
||||
]),
|
||||
exeuctionId (): string | undefined {
|
||||
return this.$route.params.id;
|
||||
},
|
||||
|
@ -311,6 +325,9 @@ export default mixins(
|
|||
openTagManager() {
|
||||
this.$store.dispatch('ui/openTagsManagerModal');
|
||||
},
|
||||
openUpdatesPanel() {
|
||||
this.$store.dispatch('ui/openUpdatesPanel');
|
||||
},
|
||||
async stopExecution () {
|
||||
const executionId = this.$store.getters.activeExecutionId;
|
||||
if (executionId === null) {
|
||||
|
@ -574,6 +591,39 @@ a.logo {
|
|||
&.expanded {
|
||||
width: $--sidebar-expanded-width;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-menu-items {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.el-menu-item.updates {
|
||||
color: $--sidebar-inactive-color;
|
||||
.item-title-root {
|
||||
font-size: 13px;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $--sidebar-active-color;
|
||||
}
|
||||
|
||||
.gift-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
<template>
|
||||
<div v-if="dialogVisible">
|
||||
<div>
|
||||
<el-drawer
|
||||
v-if="drawer"
|
||||
:direction="drawerDirection"
|
||||
:visible="visible && visibleDrawer"
|
||||
:size="drawerWidth"
|
||||
:before-close="closeDrawer"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<slot name="header" />
|
||||
</template>
|
||||
<template>
|
||||
<slot name="content"/>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-dialog
|
||||
v-else
|
||||
:visible="dialogVisible"
|
||||
:before-close="closeDialog"
|
||||
:title="title"
|
||||
|
@ -32,7 +47,12 @@ const sizeMap: {[size: string]: string} = {
|
|||
|
||||
export default Vue.extend({
|
||||
name: "Modal",
|
||||
props: ['name', 'title', 'eventBus', 'size'],
|
||||
props: ['name', 'title', 'eventBus', 'size', 'drawer', 'drawerDirection', 'drawerWidth', 'visible'],
|
||||
data() {
|
||||
return {
|
||||
visibleDrawer: this.drawer,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.onWindowKeydown);
|
||||
|
||||
|
@ -65,8 +85,18 @@ export default Vue.extend({
|
|||
this.$emit('enter');
|
||||
}
|
||||
},
|
||||
closeDialog() {
|
||||
closeDialog(callback?: () => void) {
|
||||
this.$store.commit('ui/closeTopModal');
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
closeDrawer() {
|
||||
this.visibleDrawer = false;
|
||||
setTimeout(() =>{
|
||||
this.$store.commit('ui/closeTopModal');
|
||||
this.visibleDrawer = true;
|
||||
}, 300); // delayed for closing animation to take effect
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
@ -84,6 +114,15 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-drawer__header {
|
||||
margin: 0;
|
||||
padding: 30px 30px 0 30px;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-wrapper {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="isOpen(name)"
|
||||
v-if="isOpen(name) || keepAlive"
|
||||
>
|
||||
<slot :modalName="name" :active="isActive(name)"></slot>
|
||||
<slot :modalName="name" :active="isActive(name)" :open="isOpen(name)"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -11,7 +11,7 @@ import Vue from "vue";
|
|||
|
||||
export default Vue.extend({
|
||||
name: "ModalRoot",
|
||||
props: ["name"],
|
||||
props: ["name", "keepAlive"],
|
||||
methods: {
|
||||
isActive(name: string) {
|
||||
return this.$store.getters['ui/isModalActive'](name);
|
||||
|
|
|
@ -24,17 +24,26 @@
|
|||
/>
|
||||
</template>
|
||||
</ModalRoot>
|
||||
<ModalRoot :name="VERSIONS_MODAL_KEY" :keepAlive="true">
|
||||
<template v-slot="{ modalName, open }">
|
||||
<UpdatesPanel
|
||||
:modalName="modalName"
|
||||
:visible="open"
|
||||
/>
|
||||
</template>
|
||||
</ModalRoot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY, VERSIONS_MODAL_KEY } from '@/constants';
|
||||
|
||||
import TagsManager from "@/components/TagsManager/TagsManager.vue";
|
||||
import DuplicateWorkflowDialog from "@/components/DuplicateWorkflowDialog.vue";
|
||||
import WorkflowOpen from "@/components/WorkflowOpen.vue";
|
||||
import ModalRoot from "./ModalRoot.vue";
|
||||
import UpdatesPanel from "./UpdatesPanel.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Modals",
|
||||
|
@ -43,11 +52,13 @@ export default Vue.extend({
|
|||
DuplicateWorkflowDialog,
|
||||
WorkflowOpen,
|
||||
ModalRoot,
|
||||
UpdatesPanel,
|
||||
},
|
||||
data: () => ({
|
||||
DUPLICATE_MODAL_KEY,
|
||||
TAGS_MANAGER_MODAL_KEY,
|
||||
WORKLOW_OPEN_MODAL_KEY,
|
||||
VERSIONS_MODAL_KEY,
|
||||
}),
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<NodeIcon class="node-icon" :nodeType="nodeType" size="60" :style="nodeIconStyle" :shrink="true"/>
|
||||
<NodeIcon class="node-icon" :nodeType="nodeType" size="60" :shrink="true" :disabled="this.data.disabled"/>
|
||||
</div>
|
||||
<div class="node-description">
|
||||
<div class="node-name" :title="data.name">
|
||||
|
@ -77,11 +77,6 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
|||
isExecuting (): boolean {
|
||||
return this.$store.getters.executingNode === this.data.name;
|
||||
},
|
||||
nodeIconStyle (): object {
|
||||
return {
|
||||
color: this.data.disabled ? '#ccc' : this.data.color,
|
||||
};
|
||||
},
|
||||
nodeType (): INodeTypeDescription | null {
|
||||
return this.$store.getters.nodeType(this.data.type);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template functional>
|
||||
<div :class="{[$style['node-item']]: true, [$style.bordered]: props.bordered}">
|
||||
<NodeIcon :class="$style['node-icon']" :nodeType="props.nodeType" :style="{color: props.nodeType.defaults.color}" />
|
||||
<NodeIcon :class="$style['node-icon']" :nodeType="props.nodeType" />
|
||||
<div>
|
||||
<div :class="$style.details">
|
||||
<span :class="$style.name">{{props.nodeType.displayName}}</span>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="node-icon-wrapper" :style="iconStyleData" :class="{shrink: isSvgIcon && shrink, full: !shrink}">
|
||||
<div v-if="nodeIconData !== null" class="icon">
|
||||
<img :src="nodeIconData.path" style="max-width: 100%; max-height: 100%;" v-if="nodeIconData.type === 'file'"/>
|
||||
<font-awesome-icon :icon="nodeIconData.path" v-else-if="nodeIconData.type === 'fa'" />
|
||||
<img v-if="nodeIconData.type === 'file'" :src="nodeIconData.fileBuffer || nodeIconData.path" style="max-width: 100%; max-height: 100%;" />
|
||||
<font-awesome-icon v-else :icon="nodeIconData.icon || nodeIconData.path" />
|
||||
</div>
|
||||
<div v-else class="node-icon-placeholder">
|
||||
{{nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
||||
|
@ -26,16 +26,19 @@ export default Vue.extend({
|
|||
'nodeType',
|
||||
'size',
|
||||
'shrink',
|
||||
'disabled',
|
||||
],
|
||||
computed: {
|
||||
iconStyleData (): object {
|
||||
const color = this.disabled ? '#ccc' : this.nodeType.defaults && this.nodeType.defaults.color;
|
||||
if (!this.size) {
|
||||
return {};
|
||||
return {color};
|
||||
}
|
||||
|
||||
const size = parseInt(this.size, 10);
|
||||
|
||||
return {
|
||||
color,
|
||||
width: size + 'px',
|
||||
height: size + 'px',
|
||||
'font-size': Math.floor(parseInt(this.size, 10) * 0.6) + 'px',
|
||||
|
@ -54,6 +57,10 @@ export default Vue.extend({
|
|||
return null;
|
||||
}
|
||||
|
||||
if (this.nodeType.iconData) {
|
||||
return this.nodeType.iconData;
|
||||
}
|
||||
|
||||
const restUrl = this.$store.getters.getRestUrl;
|
||||
|
||||
if (this.nodeType.icon) {
|
||||
|
@ -94,6 +101,10 @@ export default Vue.extend({
|
|||
&.full .icon {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.shrink .icon {
|
||||
|
|
15
packages/editor-ui/src/components/TimeAgo.vue
Normal file
15
packages/editor-ui/src/components/TimeAgo.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<template functional>
|
||||
<span>
|
||||
{{$options.format(props.date)}}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { format } from 'timeago.js';
|
||||
|
||||
export default {
|
||||
name: 'UpdatesPanel',
|
||||
props: ['date'],
|
||||
format,
|
||||
};
|
||||
</script>
|
122
packages/editor-ui/src/components/UpdatesPanel.vue
Normal file
122
packages/editor-ui/src/components/UpdatesPanel.vue
Normal file
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:drawer="true"
|
||||
:visible="visible"
|
||||
drawerDirection="ltr"
|
||||
drawerWidth="520px"
|
||||
>
|
||||
<template slot="header">
|
||||
<span :class="$style.title">We’ve been busy ✨</span>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<section :class="$style['description']">
|
||||
|
||||
<p v-if="currentVersion">
|
||||
You’re on {{ currentVersion.name }}, which was released
|
||||
<strong><TimeAgo :date="currentVersion.createdAt" /></strong> and is
|
||||
<strong>{{ nextVersions.length }} version{{nextVersions.length > 1 ? "s" : ""}}</strong>
|
||||
behind the latest and greatest n8n
|
||||
</p>
|
||||
|
||||
<a
|
||||
:class="$style['info-url']"
|
||||
:href="infoUrl"
|
||||
v-if="infoUrl"
|
||||
target="_blank"
|
||||
>
|
||||
<font-awesome-icon icon="info-circle"></font-awesome-icon>
|
||||
<span>How to update your n8n version</span>
|
||||
</a>
|
||||
|
||||
</section>
|
||||
<section :class="$style.versions">
|
||||
<div
|
||||
v-for="version in nextVersions"
|
||||
:key="version.name"
|
||||
:class="$style['versions-card']"
|
||||
>
|
||||
<VersionCard :version="version" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import Modal from './Modal.vue';
|
||||
import TimeAgo from './TimeAgo.vue';
|
||||
import VersionCard from './VersionCard.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'UpdatesPanel',
|
||||
components: {
|
||||
Modal,
|
||||
VersionCard,
|
||||
TimeAgo,
|
||||
},
|
||||
props: ['modalName', 'visible'],
|
||||
computed: {
|
||||
...mapGetters('versions', ['nextVersions', 'currentVersion', 'infoUrl']),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
color: $--updates-panel-text-color;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 0px 30px;
|
||||
margin-block-start: 16px;
|
||||
margin-block-end: 30px;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: $--updates-panel-description-text-color;
|
||||
font-weight: 400;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
div {
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.versions {
|
||||
background-color: $--updates-panel-dark-background-color;
|
||||
border-top: $--updates-panel-border;
|
||||
height: 100%;
|
||||
padding: 30px;
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 220px;
|
||||
}
|
||||
|
||||
.versions-card {
|
||||
margin-block-end: 15px;
|
||||
}
|
||||
|
||||
.info-url {
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
|
||||
svg {
|
||||
color: $--updates-panel-info-icon-color;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $--updates-panel-info-url-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
132
packages/editor-ui/src/components/VersionCard.vue
Normal file
132
packages/editor-ui/src/components/VersionCard.vue
Normal file
|
@ -0,0 +1,132 @@
|
|||
<template functional>
|
||||
<a v-if="props.version" :set="version = props.version" :href="version.documentationUrl" target="_blank" :class="$style.card">
|
||||
<div :class="$style.header">
|
||||
<div>
|
||||
<div :class="$style.name">
|
||||
Version {{version.name}}
|
||||
</div>
|
||||
<WarningTooltip v-if="version.hasSecurityIssue">
|
||||
<template>
|
||||
This version has a security issue.<br/>It is listed here for completeness.
|
||||
</template>
|
||||
</WarningTooltip>
|
||||
<Badge
|
||||
v-if="version.hasSecurityFix"
|
||||
text="Security update"
|
||||
type="danger"
|
||||
/>
|
||||
<Badge
|
||||
v-if="version.hasBreakingChange"
|
||||
text="Breaking changes"
|
||||
type="warning"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style['release-date']">
|
||||
Released <TimeAgo :date="version.createdAt" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.divider" v-if="version.description || (version.nodes && version.nodes.length)"></div>
|
||||
<div>
|
||||
<div v-if="version.description" v-html="version.description" :class="$style.description"></div>
|
||||
<div v-if="version.nodes && version.nodes.length > 0" :class="$style.nodes">
|
||||
<NodeIcon
|
||||
v-for="node in version.nodes"
|
||||
:key="node.name"
|
||||
:nodeType="node"
|
||||
:title="$options.nodeName(node)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import NodeIcon from './NodeIcon.vue';
|
||||
import TimeAgo from './TimeAgo.vue';
|
||||
import Badge from './Badge.vue';
|
||||
import WarningTooltip from './WarningTooltip.vue';
|
||||
import { IVersionNode } from '@/Interface';
|
||||
|
||||
Vue.component('NodeIcon', NodeIcon);
|
||||
Vue.component('TimeAgo', TimeAgo);
|
||||
Vue.component('Badge', Badge);
|
||||
Vue.component('WarningTooltip', WarningTooltip);
|
||||
|
||||
export default Vue.extend({
|
||||
components: { NodeIcon, TimeAgo, Badge, WarningTooltip },
|
||||
name: 'UpdatesPanel',
|
||||
props: ['version'],
|
||||
// @ts-ignore
|
||||
nodeName (node: IVersionNode): string {
|
||||
return node !== null ? node.displayName : 'unknown';
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.card {
|
||||
background-color: $--version-card-background-color;
|
||||
border: $--version-card-border;
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
padding: 16px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 2px 10px $--version-card-box-shadow-color;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
> div:first-child {
|
||||
flex-grow: 1;
|
||||
|
||||
> * {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
color: $--version-card-name-text-color;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-bottom: $--version-card-border;
|
||||
width: 100%;
|
||||
margin: 10px 0 15px 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 19px;
|
||||
color: $--version-card-description-text-color;
|
||||
}
|
||||
|
||||
.release-date {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
color: $--version-card-release-date-text-color;
|
||||
}
|
||||
|
||||
.nodes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 1fr);
|
||||
grid-row-gap: 12px;
|
||||
margin-block-start: 24px;
|
||||
}
|
||||
</style>
|
15
packages/editor-ui/src/components/WarningTooltip.vue
Normal file
15
packages/editor-ui/src/components/WarningTooltip.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<template functional>
|
||||
<el-tooltip effect="light" content=" " placement="top" >
|
||||
<div slot="content"><slot /></div>
|
||||
<font-awesome-icon :class="$style['icon']" icon="exclamation-triangle"></font-awesome-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.icon {
|
||||
font-size: 14px;
|
||||
height: 18px;
|
||||
color: $--warning-tooltip-color;
|
||||
}
|
||||
</style>
|
40
packages/editor-ui/src/components/mixins/newVersions.ts
Normal file
40
packages/editor-ui/src/components/mixins/newVersions.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import mixins from 'vue-typed-mixins';
|
||||
import { showMessage } from './showMessage';
|
||||
import {
|
||||
IVersion,
|
||||
} from '../../Interface';
|
||||
|
||||
export const newVersions = mixins(
|
||||
showMessage,
|
||||
).extend({
|
||||
methods: {
|
||||
async checkForNewVersions() {
|
||||
const enabled = this.$store.getters['versions/areNotificationsEnabled'];
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('versions/fetchVersions');
|
||||
|
||||
const currentVersion: IVersion | undefined = this.$store.getters['versions/currentVersion'];
|
||||
const nextVersions: IVersion[] = this.$store.getters['versions/nextVersions'];
|
||||
if (currentVersion && currentVersion.hasSecurityIssue && nextVersions.length) {
|
||||
const fixVersion = currentVersion.securityIssueFixVersion;
|
||||
let message = `Please update to latest version.`;
|
||||
if (fixVersion) {
|
||||
message = `Please update to version ${fixVersion} or higher.`;
|
||||
}
|
||||
|
||||
message = `${message} <a class="primary-color">More info</a>`;
|
||||
this.$showWarning('Critical update available', message, {
|
||||
onClick: () => {
|
||||
this.$store.dispatch('ui/openUpdatesPanel');
|
||||
},
|
||||
closeOnClick: true,
|
||||
customClass: 'clickable',
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import { Notification } from 'element-ui';
|
||||
import { ElNotificationOptions } from 'element-ui/types/notification';
|
||||
import { ElNotificationComponent, ElNotificationOptions } from 'element-ui/types/notification';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
|
@ -16,6 +16,30 @@ export const showMessage = mixins(externalHooks).extend({
|
|||
return Notification(messageData);
|
||||
},
|
||||
|
||||
$showWarning(title: string, message: string, config?: {onClick?: () => void, duration?: number, customClass?: string, closeOnClick?: boolean}) {
|
||||
let notification: ElNotificationComponent;
|
||||
if (config && config.closeOnClick) {
|
||||
const cb = config.onClick;
|
||||
config.onClick = () => {
|
||||
if (notification) {
|
||||
notification.close();
|
||||
}
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
notification = this.$showMessage({
|
||||
title,
|
||||
message,
|
||||
type: 'warning',
|
||||
...(config || {}),
|
||||
});
|
||||
|
||||
return notification;
|
||||
},
|
||||
|
||||
$getExecutionError(error?: ExecutionError) {
|
||||
// There was a problem with executing the workflow
|
||||
let errorMessage = 'There was a problem executing the workflow!';
|
||||
|
|
|
@ -14,6 +14,7 @@ export const MAX_TAG_NAME_LENGTH = 24;
|
|||
export const DUPLICATE_MODAL_KEY = 'duplicate';
|
||||
export const TAGS_MANAGER_MODAL_KEY = 'tagsManager';
|
||||
export const WORKLOW_OPEN_MODAL_KEY = 'workflowOpen';
|
||||
export const VERSIONS_MODAL_KEY = 'versions';
|
||||
|
||||
// breakpoints
|
||||
export const BREAKPOINT_SM = 768;
|
||||
|
@ -48,3 +49,6 @@ export const HIDDEN_NODES = ['n8n-nodes-base.start'];
|
|||
export const WEBHOOK_NODE_NAME = 'n8n-nodes-base.webhook';
|
||||
export const HTTP_REQUEST_NODE_NAME = 'n8n-nodes-base.httpRequest';
|
||||
export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fBVTZ3';
|
||||
|
||||
// General
|
||||
export const INSTANCE_ID_HEADER = 'n8n-instance-id';
|
|
@ -21,6 +21,7 @@ import { runExternalHook } from './components/mixins/externalHooks';
|
|||
|
||||
// @ts-ignore
|
||||
import vClickOutside from 'v-click-outside';
|
||||
import Fragment from 'vue-fragment';
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import {
|
||||
|
@ -62,6 +63,7 @@ import {
|
|||
faFileImport,
|
||||
faFilePdf,
|
||||
faFolderOpen,
|
||||
faGift,
|
||||
faHdd,
|
||||
faHome,
|
||||
faHourglass,
|
||||
|
@ -151,6 +153,7 @@ library.add(faFileExport);
|
|||
library.add(faFileImport);
|
||||
library.add(faFilePdf);
|
||||
library.add(faFolderOpen);
|
||||
library.add(faGift);
|
||||
library.add(faHdd);
|
||||
library.add(faHome);
|
||||
library.add(faHourglass);
|
||||
|
@ -194,6 +197,7 @@ library.add(faUsers);
|
|||
library.add(faClock);
|
||||
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||
Vue.use(Fragment.Plugin);
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
router.afterEach((to, from) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, VERSIONS_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import Vue from 'vue';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
import {
|
||||
|
@ -19,12 +19,18 @@ const module: Module<IUiState, IRootState> = {
|
|||
[WORKLOW_OPEN_MODAL_KEY]: {
|
||||
open: false,
|
||||
},
|
||||
[VERSIONS_MODAL_KEY]: {
|
||||
open: false,
|
||||
},
|
||||
},
|
||||
modalStack: [],
|
||||
sidebarMenuCollapsed: true,
|
||||
isPageLoading: true,
|
||||
},
|
||||
getters: {
|
||||
isVersionsOpen: (state: IUiState) => {
|
||||
return state.modals[VERSIONS_MODAL_KEY].open;
|
||||
},
|
||||
isModalOpen: (state: IUiState) => {
|
||||
return (name: string) => state.modals[name].open;
|
||||
},
|
||||
|
@ -58,6 +64,9 @@ const module: Module<IUiState, IRootState> = {
|
|||
openDuplicateModal: async (context: ActionContext<IUiState, IRootState>) => {
|
||||
context.commit('openModal', DUPLICATE_MODAL_KEY);
|
||||
},
|
||||
openUpdatesPanel: async (context: ActionContext<IUiState, IRootState>) => {
|
||||
context.commit('openModal', VERSIONS_MODAL_KEY);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
62
packages/editor-ui/src/modules/versions.ts
Normal file
62
packages/editor-ui/src/modules/versions.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { getNextVersions } from '@/api/versions';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
import {
|
||||
IRootState,
|
||||
IVersion,
|
||||
IVersionsState,
|
||||
} from '../Interface';
|
||||
|
||||
const module: Module<IVersionsState, IRootState> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
versionNotificationSettings: {
|
||||
enabled: false,
|
||||
endpoint: '',
|
||||
infoUrl: '',
|
||||
},
|
||||
nextVersions: [],
|
||||
currentVersion: undefined,
|
||||
},
|
||||
getters: {
|
||||
hasVersionUpdates(state: IVersionsState) {
|
||||
return state.nextVersions.length > 0;
|
||||
},
|
||||
nextVersions(state: IVersionsState) {
|
||||
return state.nextVersions;
|
||||
},
|
||||
currentVersion(state: IVersionsState) {
|
||||
return state.currentVersion;
|
||||
},
|
||||
areNotificationsEnabled(state: IVersionsState) {
|
||||
return state.versionNotificationSettings.enabled;
|
||||
},
|
||||
infoUrl(state: IVersionsState) {
|
||||
return state.versionNotificationSettings.infoUrl;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setVersions(state: IVersionsState, {versions, currentVersion}: {versions: IVersion[], currentVersion: string}) {
|
||||
state.nextVersions = versions.filter((version) => version.name !== currentVersion);
|
||||
state.currentVersion = versions.find((version) => version.name === currentVersion);
|
||||
},
|
||||
setVersionNotificationSettings(state: IVersionsState, settings: {enabled: true, endpoint: string, infoUrl: string}) {
|
||||
state.versionNotificationSettings = settings;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async fetchVersions(context: ActionContext<IVersionsState, IRootState>) {
|
||||
try {
|
||||
const { enabled, endpoint } = context.state.versionNotificationSettings;
|
||||
if (enabled && endpoint) {
|
||||
const currentVersion = context.rootState.versionCli;
|
||||
const instanceId = context.rootState.instanceId;
|
||||
const versions = await getNextVersions(endpoint, currentVersion, instanceId);
|
||||
context.commit('setVersions', {versions, currentVersion});
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default module;
|
|
@ -28,6 +28,16 @@ $--custom-success-text : #40c351;
|
|||
$--custom-warning-background : #ffffe5;
|
||||
$--custom-warning-text : #eb9422;
|
||||
|
||||
// Badge
|
||||
$--badge-danger-color: #f45959;
|
||||
$--badge-danger-background-color: #fef0f0;
|
||||
$--badge-danger-border-color: #fde2e2;
|
||||
$--badge-warning-background-color: rgba(255, 229, 100, 0.3);
|
||||
$--badge-warning-color: #6b5900;
|
||||
|
||||
// Warning tooltip
|
||||
$--warning-tooltip-color: #ff8080;
|
||||
|
||||
$--custom-node-view-background : #faf9fe;
|
||||
|
||||
// Table
|
||||
|
@ -44,8 +54,18 @@ $--custom-input-border-shadow: 1px solid $--custom-input-border-color;
|
|||
|
||||
$--header-height: 65px;
|
||||
|
||||
// sidebar
|
||||
$--sidebar-width: 65px;
|
||||
$--sidebar-expanded-width: 200px;
|
||||
$--sidebar-inactive-color: #909399;
|
||||
$--sidebar-active-color: $--color-primary;
|
||||
|
||||
// gifts notification
|
||||
$--gift-notification-active-color: $--color-primary;
|
||||
$--gift-notification-inner-color: $--color-primary;
|
||||
$--gift-notification-outer-color: #fff;
|
||||
|
||||
// tags manager
|
||||
$--tags-manager-min-height: 300px;
|
||||
|
||||
// based on element.io breakpoints
|
||||
|
@ -83,3 +103,23 @@ $--node-creator-description-color: #7d7d87;
|
|||
// trigger icon
|
||||
$--trigger-icon-border-color: #dcdfe6;
|
||||
$--trigger-icon-background-color: #fff;
|
||||
|
||||
// drawer
|
||||
$--drawer-background-color: #fff;
|
||||
|
||||
// updates-panel
|
||||
$--updates-panel-info-icon-color: #909399;
|
||||
$--updates-panel-info-url-color: $--color-primary;
|
||||
$--updates-panel-border: 1px #dbdfe7 solid;
|
||||
$--updates-panel-dark-background-color: #f8f9fb;
|
||||
$--updates-panel-description-text-color: #7d7d87;
|
||||
$--updates-panel-text-color: #555;
|
||||
|
||||
// versions card
|
||||
$--version-card-name-text-color: #666;
|
||||
$--version-card-background-color: #fff;
|
||||
$--version-card-border: 1px #dbdfe7 solid;
|
||||
$--version-card-description-text-color: #7d7d87;
|
||||
$--version-card-release-date-text-color: #909399;
|
||||
$--version-card-box-shadow-color: rgba(109, 48, 40, 0.07);
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
import tags from './modules/tags';
|
||||
import ui from './modules/ui';
|
||||
import workflows from './modules/workflows';
|
||||
import versions from './modules/versions';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
|
@ -86,12 +87,14 @@ const state: IRootState = {
|
|||
tags: [],
|
||||
},
|
||||
sidebarMenuItems: [],
|
||||
instanceId: '',
|
||||
};
|
||||
|
||||
const modules = {
|
||||
tags,
|
||||
ui,
|
||||
workflows,
|
||||
versions,
|
||||
};
|
||||
|
||||
export const store = new Vuex.Store({
|
||||
|
@ -546,6 +549,9 @@ export const store = new Vuex.Store({
|
|||
setVersionCli(state, version: string) {
|
||||
Vue.set(state, 'versionCli', version);
|
||||
},
|
||||
setInstanceId(state, instanceId: string) {
|
||||
Vue.set(state, 'instanceId', instanceId);
|
||||
},
|
||||
setOauthCallbackUrls(state, urls: IDataObject) {
|
||||
Vue.set(state, 'oauthCallbackUrls', urls);
|
||||
},
|
||||
|
|
|
@ -125,6 +125,7 @@ import { moveNodeWorkflow } from '@/components/mixins/moveNodeWorkflow';
|
|||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
import { newVersions } from '@/components/mixins/newVersions';
|
||||
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import { workflowRun } from '@/components/mixins/workflowRun';
|
||||
|
@ -198,6 +199,7 @@ export default mixins(
|
|||
titleChange,
|
||||
workflowHelpers,
|
||||
workflowRun,
|
||||
newVersions,
|
||||
)
|
||||
.extend({
|
||||
name: 'NodeView',
|
||||
|
@ -2200,8 +2202,10 @@ export default mixins(
|
|||
this.$store.commit('setExecutionTimeout', settings.executionTimeout);
|
||||
this.$store.commit('setMaxExecutionTimeout', settings.maxExecutionTimeout);
|
||||
this.$store.commit('setVersionCli', settings.versionCli);
|
||||
this.$store.commit('setInstanceId', settings.instanceId);
|
||||
this.$store.commit('setOauthCallbackUrls', settings.oauthCallbackUrls);
|
||||
this.$store.commit('setN8nMetadata', settings.n8nMetadata || {});
|
||||
this.$store.commit('versions/setVersionNotificationSettings', settings.versionNotifications);
|
||||
},
|
||||
async loadNodeTypes (): Promise<void> {
|
||||
const nodeTypes = await this.restApi().getNodeTypes();
|
||||
|
@ -2228,6 +2232,7 @@ export default mixins(
|
|||
},
|
||||
},
|
||||
|
||||
|
||||
async mounted () {
|
||||
this.$root.$on('importWorkflowData', async (data: IDataObject) => {
|
||||
const resData = await this.importWorkflowData(data.data as IWorkflowDataUpdate);
|
||||
|
@ -2267,6 +2272,10 @@ export default mixins(
|
|||
this.$showError(error, 'Init Problem', 'There was a problem initializing the workflow:');
|
||||
}
|
||||
this.stopLoading();
|
||||
|
||||
setTimeout(() => {
|
||||
this.checkForNewVersions();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
this.$externalHooks().run('nodeView.mount');
|
||||
|
|
Loading…
Reference in a new issue