n8n/packages/editor-ui/src/views/SettingsLogStreamingView.vue

265 lines
8.3 KiB
Vue
Raw Normal View History

feat: Add global event bus (#4860) * fix branch * fix deserialize, add filewriter * add catchAll eventGroup/Name * adding simple Redis sender and receiver to eventbus * remove native node threads * improve eventbus * refactor and simplify * more refactoring and syslog client * more refactor, improved endpoints and eventbus * remove local broker and receivers from mvp * destination de/serialization * create MessageEventBusDestinationEntity * db migrations, load destinations at startup * add delete destination endpoint * pnpm merge and circular import fix * delete destination fix * trigger log file shuffle after size reached * add environment variables for eventbus * reworking event messages * serialize to thread fix * some refactor and lint fixing * add emit to eventbus * cleanup and fix sending unsent * quicksave frontend trial * initial EventTree vue component * basic log streaming settings in vue * http request code merge * create destination settings modals * fix eventmessage options types * credentials are loaded * fix and clean up frontend code * move request code to axios * update lock file * merge fix * fix redis build * move destination interfaces into workflow pkg * revive sentry as destination * migration fixes and frontend cleanup * N8N-5777 / N8N-5789 N8N-5788 * N8N-5784 * N8N-5782 removed event levels * N8N-5790 sentry destination cleanup * N8N-5786 and refactoring * N8N-5809 and refactor/cleanup * UI fixes and anonymize renaming * N8N-5837 * N8N-5834 * fix no-items UI issues * remove card / settings label in modal * N8N-5842 fix * disable webhook auth for now and update ui * change sidebar to tabs * remove payload option * extend audit events with more user data * N8N-5853 and UI revert to sidebar * remove redis destination * N8N-5864 / N8N-5868 / N8N-5867 / N8N-5865 * ui and licensing fixes * add node events and info bubbles to frontend * ui wording changes * frontend tests * N8N-5896 and ee rename * improves backend tests * merge fix * fix backend test * make linter happy * remove unnecessary cfg / limit actions to owners * fix multiple sentry DSN and anon bug * eslint fix * more tests and fixes * merge fix * fix workflow audit events * remove 'n8n.workflow.execution.error' event * merge fix * lint fix * lint fix * review fixes * fix merge * prettier fixes * merge * review changes * use loggerproxy * remove catch from internal hook promises * fix tests * lint fix * include review PR changes * review changes * delete duplicate lines from a bad merge * decouple log-streaming UI options from public API * logstreaming -> log-streaming for consistency * do not make unnecessary api calls when log streaming is disabled * prevent sentryClient.close() from being called if init failed * fix the e2e test for log-streaming * review changes * cleanup * use `private` for one last private property * do not use node prefix package names.. just yet * remove unused import * fix the tests because there is a folder called `events`, tsc-alias is messing up all imports for native events module. https://github.com/justkey007/tsc-alias/issues/152 Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
2023-01-04 00:47:48 -08:00
<template>
<div>
<div :class="$style.header">
<div class="mb-2xl">
<n8n-heading size="2xlarge">
{{ $locale.baseText(`settings.log-streaming.heading`) }}
</n8n-heading>
<template v-if="environment !== 'production'">
<strong>&nbsp;&nbsp;&nbsp;&nbsp;Disable License ({{ environment }})&nbsp;</strong>
<el-switch v-model="disableLicense" size="large" data-test-id="disable-license-toggle" />
</template>
</div>
</div>
<template v-if="isLicensed">
<div class="mb-l">
<n8n-info-tip theme="info" type="note">
<template>
<span v-html="$locale.baseText('settings.log-streaming.infoText')"></span>
</template>
</n8n-info-tip>
</div>
<template v-if="storeHasItems()">
<el-row
:gutter="10"
v-for="item in sortedItemKeysByLabel"
:key="item.key"
:class="$style.destinationItem"
>
<el-col v-if="logStreamingStore.items[item.key]?.destination">
<event-destination-card
:destination="logStreamingStore.items[item.key]?.destination"
:eventBus="eventBus"
:isInstanceOwner="isInstanceOwner"
@remove="onRemove(logStreamingStore.items[item.key]?.destination?.id)"
@edit="onEdit(logStreamingStore.items[item.key]?.destination?.id)"
/>
</el-col>
</el-row>
<div class="mt-m text-right">
<n8n-button v-if="isInstanceOwner" size="large" @click="addDestination">
{{ $locale.baseText(`settings.log-streaming.add`) }}
</n8n-button>
</div>
</template>
<template v-else>
<div :class="$style.actionBoxContainer" data-test-id="action-box-licensed">
<n8n-action-box
:buttonText="$locale.baseText(`settings.log-streaming.add`)"
@click="addDestination"
>
<template #heading>
<span v-html="$locale.baseText(`settings.log-streaming.addFirstTitle`)" />
</template>
</n8n-action-box>
</div>
</template>
</template>
<template v-else>
<div v-if="$locale.baseText('settings.log-streaming.infoText')" class="mb-l">
<n8n-info-tip theme="info" type="note">
<template>
<span v-html="$locale.baseText('settings.log-streaming.infoText')"></span>
</template>
</n8n-info-tip>
</div>
<div :class="$style.actionBoxContainer" data-test-id="action-box-unlicensed">
<n8n-action-box
:description="$locale.baseText('settings.log-streaming.actionBox.description')"
:buttonText="$locale.baseText('settings.log-streaming.actionBox.button')"
@click="onContactUsClicked"
>
<template #heading>
<span v-html="$locale.baseText('settings.log-streaming.actionBox.title')" />
</template>
</n8n-action-box>
</div>
</template>
</div>
</template>
<script lang="ts">
import { v4 as uuid } from 'uuid';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import { useWorkflowsStore } from '../stores/workflows';
import { useUsersStore } from '../stores/users';
import { useCredentialsStore } from '../stores/credentials';
import { useLogStreamingStore } from '../stores/logStreamingStore';
import { useSettingsStore } from '../stores/settings';
import { useUIStore } from '../stores/ui';
import { LOG_STREAM_MODAL_KEY, EnterpriseEditionFeature } from '../constants';
import Vue from 'vue';
import { restApi } from '../mixins/restApi';
import {
deepCopy,
defaultMessageEventBusDestinationOptions,
MessageEventBusDestinationOptions,
} from 'n8n-workflow';
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
import EventDestinationCard from '@/components/SettingsLogStreaming/EventDestinationCard.ee.vue';
export default mixins(restApi).extend({
name: 'SettingsLogStreamingView',
props: {},
components: {
PageViewLayout,
EventDestinationCard,
},
data() {
return {
eventBus: new Vue(),
destinations: Array<MessageEventBusDestinationOptions>,
disableLicense: false,
allDestinations: [] as MessageEventBusDestinationOptions[],
isInstanceOwner: false,
};
},
async mounted() {
if (!this.isLicensed) return;
this.isInstanceOwner = this.usersStore.currentUser?.globalRole?.name === 'owner';
// Prepare credentialsStore so modals can pick up credentials
await this.credentialsStore.fetchCredentialTypes(false);
await this.credentialsStore.fetchAllCredentials();
this.uiStore.nodeViewInitialized = false;
// fetch Destination data from the backend
await this.getDestinationDataFromREST();
// since we are not really integrated into the hooks, we listen to the store and refresh the destinations
this.logStreamingStore.$onAction(({ name, after }) => {
if (name === 'removeDestination' || name === 'updateDestination') {
after(async () => {
this.$forceUpdate();
});
}
});
// refresh when a modal closes
this.eventBus.$on('destinationWasSaved', async () => {
this.$forceUpdate();
});
// listen to remove emission
this.eventBus.$on('remove', async (destinationId: string) => {
await this.onRemove(destinationId);
});
// listen to modal closing and remove nodes from store
this.eventBus.$on('closing', async (destinationId: string) => {
this.workflowsStore.removeAllNodes({ setStateDirty: false, removePinData: true });
this.uiStore.stateIsDirty = false;
});
},
computed: {
...mapStores(
useSettingsStore,
useLogStreamingStore,
useWorkflowsStore,
useUIStore,
useUsersStore,
useCredentialsStore,
),
sortedItemKeysByLabel() {
const sortedKeys: Array<{ label: string; key: string }> = [];
for (const [key, value] of Object.entries(this.logStreamingStore.items)) {
sortedKeys.push({ key, label: value.destination?.label ?? 'Destination' });
}
return sortedKeys.sort((a, b) => a.label.localeCompare(b.label));
},
environment() {
return process.env.NODE_ENV;
},
isLicensed(): boolean {
if (this.disableLicense === true) return false;
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.LogStreaming);
},
},
methods: {
async getDestinationDataFromREST(): Promise<any> {
this.logStreamingStore.clearEventNames();
this.logStreamingStore.clearDestinationItemTrees();
this.allDestinations = [];
const eventNamesData = await this.restApi().makeRestApiRequest('get', '/eventbus/eventnames');
if (eventNamesData) {
for (const eventName of eventNamesData) {
this.logStreamingStore.addEventName(eventName);
}
}
const destinationData: MessageEventBusDestinationOptions[] =
await this.restApi().makeRestApiRequest('get', '/eventbus/destination');
if (destinationData) {
for (const destination of destinationData) {
this.logStreamingStore.addDestination(destination);
this.allDestinations.push(destination);
}
}
this.$forceUpdate();
},
onContactUsClicked() {
window.open('mailto:sales@n8n.io', '_blank');
this.$telemetry.track('user clicked contact us button', {
feature: EnterpriseEditionFeature.LogStreaming,
});
},
storeHasItems(): boolean {
return this.logStreamingStore.items && Object.keys(this.logStreamingStore.items).length > 0;
},
async addDestination() {
const newDestination = deepCopy(defaultMessageEventBusDestinationOptions);
newDestination.id = uuid();
this.logStreamingStore.addDestination(newDestination);
this.uiStore.openModalWithData({
name: LOG_STREAM_MODAL_KEY,
data: {
destination: newDestination,
isNew: true,
eventBus: this.eventBus,
},
});
},
async onRemove(destinationId?: string) {
if (!destinationId) return;
await this.restApi().makeRestApiRequest(
'DELETE',
`/eventbus/destination?id=${destinationId}`,
);
this.logStreamingStore.removeDestination(destinationId);
const foundNode = this.workflowsStore.getNodeByName(destinationId);
if (foundNode) {
this.workflowsStore.removeNode(foundNode);
}
},
async onEdit(destinationId?: string) {
if (!destinationId) return;
const editDestination = this.logStreamingStore.getDestination(destinationId);
if (editDestination) {
this.uiStore.openModalWithData({
name: LOG_STREAM_MODAL_KEY,
data: {
destination: editDestination,
isNew: false,
eventBus: this.eventBus,
},
});
}
},
},
});
</script>
<style lang="scss" module>
.header {
display: flex;
flex-direction: column;
align-items: flex-start;
white-space: nowrap;
*:first-child {
flex-grow: 1;
}
}
.destinationItem {
margin-bottom: 0.5em;
}
</style>