From 98ec23544b6fe57e3bf9d5b19b8f55c7432eba21 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Thu, 22 Jul 2021 10:22:17 +0200 Subject: [PATCH] :sparkles: 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" 480f969e07c3282c50bc326babbc5812baac5dae * 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 * :zap: Display node-name on hover * :zap: Formatting fix Co-authored-by: MedAliMarz Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> Co-authored-by: Jan Oberhauser --- packages/cli/commands/start.ts | 3 + packages/cli/config/index.ts | 21 +++ packages/cli/src/Interfaces.ts | 7 + packages/cli/src/Server.ts | 16 ++- packages/editor-ui/package.json | 4 +- packages/editor-ui/src/Interface.ts | 38 +++++ packages/editor-ui/src/api/helpers.ts | 5 +- packages/editor-ui/src/api/versions.ts | 9 ++ packages/editor-ui/src/components/Badge.vue | 51 +++++++ .../src/components/GiftNotificationIcon.vue | 36 +++++ .../editor-ui/src/components/MainSidebar.vue | 50 +++++++ packages/editor-ui/src/components/Modal.vue | 45 +++++- .../editor-ui/src/components/ModalRoot.vue | 6 +- packages/editor-ui/src/components/Modals.vue | 13 +- packages/editor-ui/src/components/Node.vue | 7 +- .../src/components/NodeCreator/NodeItem.vue | 2 +- .../editor-ui/src/components/NodeIcon.vue | 17 ++- packages/editor-ui/src/components/TimeAgo.vue | 15 ++ .../editor-ui/src/components/UpdatesPanel.vue | 122 ++++++++++++++++ .../editor-ui/src/components/VersionCard.vue | 132 ++++++++++++++++++ .../src/components/WarningTooltip.vue | 15 ++ .../src/components/mixins/newVersions.ts | 40 ++++++ .../src/components/mixins/showMessage.ts | 26 +++- packages/editor-ui/src/constants.ts | 4 + packages/editor-ui/src/main.ts | 4 + packages/editor-ui/src/modules/ui.ts | 11 +- packages/editor-ui/src/modules/versions.ts | 62 ++++++++ .../editor-ui/src/n8n-theme-variables.scss | 40 ++++++ packages/editor-ui/src/store.ts | 8 +- packages/editor-ui/src/views/NodeView.vue | 9 ++ 30 files changed, 793 insertions(+), 25 deletions(-) create mode 100644 packages/editor-ui/src/api/versions.ts create mode 100644 packages/editor-ui/src/components/Badge.vue create mode 100644 packages/editor-ui/src/components/GiftNotificationIcon.vue create mode 100644 packages/editor-ui/src/components/TimeAgo.vue create mode 100644 packages/editor-ui/src/components/UpdatesPanel.vue create mode 100644 packages/editor-ui/src/components/VersionCard.vue create mode 100644 packages/editor-ui/src/components/WarningTooltip.vue create mode 100644 packages/editor-ui/src/components/mixins/newVersions.ts create mode 100644 packages/editor-ui/src/modules/versions.ts diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index 023e3e827f..bb6e64f56f 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -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}"`); diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index 0da11e786a..476cbbcfa7 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -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 diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index e1c09cf55e..d2aff0eb86 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -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 { diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index b36a0fe0f9..0abd3f60f1 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -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; +} diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 4486c5c02d..0acc895567 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -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", diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index cc3f89b8c6..6151d7e37e 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -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 { } diff --git a/packages/editor-ui/src/api/helpers.ts b/packages/editor-ui/src/api/helpers.ts index 739242c12a..1ae753db1c 100644 --- a/packages/editor-ui/src/api/helpers.ts +++ b/packages/editor-ui/src/api/helpers.ts @@ -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}); } diff --git a/packages/editor-ui/src/api/versions.ts b/packages/editor-ui/src/api/versions.ts new file mode 100644 index 0000000000..009f6bc4b7 --- /dev/null +++ b/packages/editor-ui/src/api/versions.ts @@ -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 { + const headers = {[INSTANCE_ID_HEADER as string] : instanceId}; + return await get(endpoint, version, {}, headers); +} diff --git a/packages/editor-ui/src/components/Badge.vue b/packages/editor-ui/src/components/Badge.vue new file mode 100644 index 0000000000..22fa536c7b --- /dev/null +++ b/packages/editor-ui/src/components/Badge.vue @@ -0,0 +1,51 @@ + + + + + \ No newline at end of file diff --git a/packages/editor-ui/src/components/GiftNotificationIcon.vue b/packages/editor-ui/src/components/GiftNotificationIcon.vue new file mode 100644 index 0000000000..e10fa9c5ae --- /dev/null +++ b/packages/editor-ui/src/components/GiftNotificationIcon.vue @@ -0,0 +1,36 @@ + + + \ No newline at end of file diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index cadabe7884..a8d4d14fcc 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -127,6 +127,14 @@ + @@ -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%; + } } diff --git a/packages/editor-ui/src/components/Modal.vue b/packages/editor-ui/src/components/Modal.vue index 274d6fc942..e73daea116 100644 --- a/packages/editor-ui/src/components/Modal.vue +++ b/packages/editor-ui/src/components/Modal.vue @@ -1,6 +1,21 @@