mirror of
https://github.com/n8n-io/n8n.git
synced 2024-10-06 01:27:49 +03:00
feat(editor): Overhaul document title management (#10999)
This commit is contained in:
parent
63e6f1fa38
commit
bb2895689f
@ -34,7 +34,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
|
||||
import { saveAs } from 'file-saver';
|
||||
import { useTitleChange } from '@/composables/useTitleChange';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { getResourcePermissions } from '@/permissions';
|
||||
@ -87,7 +87,7 @@ const locale = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const message = useMessage();
|
||||
const toast = useToast();
|
||||
const titleChange = useTitleChange();
|
||||
const documentTitle = useDocumentTitle();
|
||||
const workflowHelpers = useWorkflowHelpers({ router });
|
||||
|
||||
const isTagsEditEnabled = ref(false);
|
||||
@ -558,7 +558,7 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
|
||||
}
|
||||
uiStore.stateIsDirty = false;
|
||||
// Reset tab title since workflow is deleted.
|
||||
titleChange.titleReset();
|
||||
documentTitle.reset();
|
||||
toast.showMessage({
|
||||
title: locale.baseText('mainSidebar.showMessage.handleSelect1.title'),
|
||||
type: 'success',
|
||||
|
@ -10,7 +10,7 @@ import { useI18n } from '@/composables/useI18n';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useOrchestrationStore } from '@/stores/orchestration.store';
|
||||
import { setPageTitle } from '@/utils/htmlUtils';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import WorkerCard from './Workers/WorkerCard.ee.vue';
|
||||
import { usePushConnection } from '@/composables/usePushConnection';
|
||||
import { usePushConnectionStore } from '@/stores/pushConnection.store';
|
||||
@ -36,6 +36,7 @@ export default defineComponent({
|
||||
i18n,
|
||||
pushConnection,
|
||||
...useToast(),
|
||||
documentTitle: useDocumentTitle(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -58,7 +59,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
setPageTitle(`n8n - ${this.pageTitle}`);
|
||||
this.documentTitle.set(this.pageTitle);
|
||||
|
||||
this.$telemetry.track('User viewed worker view', {
|
||||
instance_id: this.rootStore.instanceId,
|
||||
|
@ -0,0 +1,31 @@
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { FrontendSettings } from '@n8n/api-types';
|
||||
import { useDocumentTitle } from '../useDocumentTitle';
|
||||
|
||||
const settings = mock<FrontendSettings>({ releaseChannel: 'stable' });
|
||||
vi.mock('@/stores/settings.store', () => ({
|
||||
useSettingsStore: vi.fn(() => ({ settings })),
|
||||
}));
|
||||
|
||||
describe('useDocumentTitle', () => {
|
||||
it('should set the document title', () => {
|
||||
const { set } = useDocumentTitle();
|
||||
set('Test Title');
|
||||
expect(document.title).toBe('Test Title - n8n');
|
||||
});
|
||||
|
||||
it('should reset the document title', () => {
|
||||
const { set, reset } = useDocumentTitle();
|
||||
set('Test Title');
|
||||
reset();
|
||||
expect(document.title).toBe('Workflow Automation - n8n');
|
||||
});
|
||||
|
||||
it('should use the correct prefix for the release channel', () => {
|
||||
settings.releaseChannel = 'beta';
|
||||
const { set } = useDocumentTitle();
|
||||
set('Test Title');
|
||||
expect(document.title).toBe('Test Title - n8n[BETA]');
|
||||
});
|
||||
});
|
@ -52,6 +52,7 @@ vi.mock('@/composables/useWorkflowHelpers', () => ({
|
||||
getCurrentWorkflow: vi.fn(),
|
||||
saveCurrentWorkflow: vi.fn(),
|
||||
getWorkflowDataToSave: vi.fn(),
|
||||
setDocumentTitle: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
@ -61,10 +62,6 @@ vi.mock('@/composables/useNodeHelpers', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/composables/useTitleChange', () => ({
|
||||
useTitleChange: vi.fn().mockReturnValue({ titleSet: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('vue-router', async (importOriginal) => {
|
||||
const { RouterLink } = await importOriginal<typeof router>();
|
||||
return {
|
||||
|
21
packages/editor-ui/src/composables/useDocumentTitle.ts
Normal file
21
packages/editor-ui/src/composables/useDocumentTitle.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
|
||||
const DEFAULT_TITLE = 'Workflow Automation';
|
||||
|
||||
export function useDocumentTitle() {
|
||||
const settingsStore = useSettingsStore();
|
||||
const { releaseChannel } = settingsStore.settings;
|
||||
const suffix =
|
||||
!releaseChannel || releaseChannel === 'stable' ? 'n8n' : `n8n[${releaseChannel.toUpperCase()}]`;
|
||||
|
||||
const set = (title: string) => {
|
||||
const sections = [title || DEFAULT_TITLE, suffix];
|
||||
document.title = sections.join(' - ');
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
set('');
|
||||
};
|
||||
|
||||
return { set, reset };
|
||||
}
|
@ -18,7 +18,6 @@ import type { PushMessage, PushPayload } from '@n8n/api-types';
|
||||
|
||||
import type { IExecutionResponse, IExecutionsCurrentSummaryExtended } from '@/Interface';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import { useTitleChange } from '@/composables/useTitleChange';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
|
||||
import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils';
|
||||
@ -43,7 +42,6 @@ type IPushDataExecutionFinishedPayload = PushPayload<'executionFinished'>;
|
||||
export function usePushConnection({ router }: { router: ReturnType<typeof useRouter> }) {
|
||||
const workflowHelpers = useWorkflowHelpers({ router });
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const titleChange = useTitleChange();
|
||||
const toast = useToast();
|
||||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
@ -324,7 +322,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
|
||||
}
|
||||
|
||||
// Workflow did start but had been put to wait
|
||||
titleChange.titleSet(workflow.name as string, 'IDLE');
|
||||
workflowHelpers.setDocumentTitle(workflow.name as string, 'IDLE');
|
||||
toast.showToast({
|
||||
title: 'Workflow started waiting',
|
||||
message: `${action} <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/" target="_blank">More info</a>`,
|
||||
@ -333,7 +331,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
|
||||
dangerouslyUseHTMLString: true,
|
||||
});
|
||||
} else if (runDataExecuted.finished !== true) {
|
||||
titleChange.titleSet(workflow.name as string, 'ERROR');
|
||||
workflowHelpers.setDocumentTitle(workflow.name as string, 'ERROR');
|
||||
|
||||
if (
|
||||
runDataExecuted.data.resultData.error?.name === 'ExpressionError' &&
|
||||
@ -442,7 +440,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
|
||||
}
|
||||
} else {
|
||||
// Workflow did execute without a problem
|
||||
titleChange.titleSet(workflow.name as string, 'IDLE');
|
||||
workflowHelpers.setDocumentTitle(workflow.name as string, 'IDLE');
|
||||
|
||||
const execution = workflowsStore.getWorkflowExecution;
|
||||
if (execution?.executedNode) {
|
||||
|
@ -20,7 +20,6 @@ import { useToast } from '@/composables/useToast';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
|
||||
import { CHAT_TRIGGER_NODE_TYPE, WORKFLOW_LM_CHAT_MODAL_KEY } from '@/constants';
|
||||
import { useTitleChange } from '@/composables/useTitleChange';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
@ -41,7 +40,6 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
||||
const workflowHelpers = useWorkflowHelpers({ router: useRunWorkflowOpts.router });
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
const { titleSet } = useTitleChange();
|
||||
|
||||
const rootStore = useRootStore();
|
||||
const uiStore = useUIStore();
|
||||
@ -93,7 +91,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
||||
return;
|
||||
}
|
||||
|
||||
titleSet(workflow.name as string, 'EXECUTING');
|
||||
workflowHelpers.setDocumentTitle(workflow.name as string, 'EXECUTING');
|
||||
|
||||
toast.clearAllStickyNotifications();
|
||||
|
||||
@ -309,7 +307,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
||||
|
||||
return runWorkflowApiResponse;
|
||||
} catch (error) {
|
||||
titleSet(workflow.name as string, 'ERROR');
|
||||
workflowHelpers.setDocumentTitle(workflow.name as string, 'ERROR');
|
||||
toast.showError(error, i18n.baseText('workflowRun.showError.title'));
|
||||
return undefined;
|
||||
}
|
||||
@ -387,7 +385,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
||||
workflowsStore.executingNode.length = 0;
|
||||
uiStore.removeActiveAction('workflowRunning');
|
||||
|
||||
titleSet(workflowsStore.workflowName, 'IDLE');
|
||||
workflowHelpers.setDocumentTitle(workflowsStore.workflowName, 'IDLE');
|
||||
toast.showMessage({
|
||||
title: i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.title'),
|
||||
message: i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.message'),
|
||||
@ -408,7 +406,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
||||
retryOf: execution.retryOf,
|
||||
};
|
||||
workflowsStore.finishActiveExecution(pushData);
|
||||
titleSet(execution.workflowData.name, 'IDLE');
|
||||
workflowHelpers.setDocumentTitle(execution.workflowData.name, 'IDLE');
|
||||
workflowsStore.executingNode.length = 0;
|
||||
workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse);
|
||||
uiStore.removeActiveAction('workflowRunning');
|
||||
|
@ -1,30 +0,0 @@
|
||||
import type { WorkflowTitleStatus } from '@/Interface';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
|
||||
export function useTitleChange() {
|
||||
const prependBeta = (title: string) => {
|
||||
const settingsStore = useSettingsStore();
|
||||
const { releaseChannel } = settingsStore.settings;
|
||||
return releaseChannel === 'stable' ? title : `[${releaseChannel.toUpperCase()}] ${title}`;
|
||||
};
|
||||
|
||||
const titleSet = (workflow: string, status: WorkflowTitleStatus) => {
|
||||
let icon = '⚠️';
|
||||
if (status === 'EXECUTING') {
|
||||
icon = '🔄';
|
||||
} else if (status === 'IDLE') {
|
||||
icon = '▶️';
|
||||
}
|
||||
|
||||
window.document.title = prependBeta(`n8n - ${icon} ${workflow}`);
|
||||
};
|
||||
|
||||
const titleReset = () => {
|
||||
window.document.title = prependBeta('n8n - Workflow Automation');
|
||||
};
|
||||
|
||||
return {
|
||||
titleSet,
|
||||
titleReset,
|
||||
};
|
||||
}
|
@ -37,6 +37,7 @@ import type {
|
||||
IWorkflowDataUpdate,
|
||||
IWorkflowDb,
|
||||
TargetItem,
|
||||
WorkflowTitleStatus,
|
||||
XYPosition,
|
||||
} from '@/Interface';
|
||||
|
||||
@ -57,6 +58,7 @@ import { getSourceItems } from '@/utils/pairedItemUtils';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
@ -458,6 +460,17 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
||||
const message = useMessage();
|
||||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const setDocumentTitle = (workflowName: string, status: WorkflowTitleStatus) => {
|
||||
let icon = '⚠️';
|
||||
if (status === 'EXECUTING') {
|
||||
icon = '🔄';
|
||||
} else if (status === 'IDLE') {
|
||||
icon = '▶️';
|
||||
}
|
||||
documentTitle.set(`${icon} ${workflowName}`);
|
||||
};
|
||||
|
||||
function getNodeTypesMaxCount() {
|
||||
const nodes = workflowsStore.allNodes;
|
||||
@ -1172,6 +1185,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
||||
}
|
||||
|
||||
return {
|
||||
setDocumentTitle,
|
||||
resolveParameter,
|
||||
resolveRequiredParameters,
|
||||
getCurrentWorkflow,
|
||||
|
@ -17,7 +17,6 @@ import { useUIStore } from './ui.store';
|
||||
import { useUsersStore } from './users.store';
|
||||
import { useVersionsStore } from './versions.store';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import { useTitleChange } from '@/composables/useTitleChange';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { i18n } from '@/plugins/i18n';
|
||||
|
||||
@ -258,9 +257,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
||||
|
||||
ExpressionEvaluatorProxy.setEvaluator(settings.value.expressions.evaluator);
|
||||
|
||||
// Re-compute title since settings are now available
|
||||
useTitleChange().titleReset();
|
||||
|
||||
initialized.value = true;
|
||||
} catch (e) {
|
||||
showToast({
|
||||
|
@ -48,10 +48,6 @@ export const sanitizeIfString = <T>(message: T): string | T => {
|
||||
return message;
|
||||
};
|
||||
|
||||
export function setPageTitle(title: string) {
|
||||
window.document.title = title;
|
||||
}
|
||||
|
||||
export function convertRemToPixels(rem: string) {
|
||||
return parseInt(rem, 10) * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import ProjectTabs from '@/components/Projects/ProjectTabs.vue';
|
||||
import useEnvironmentsStore from '@/stores/environments.ee.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { getResourcePermissions } from '@/permissions';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialsView',
|
||||
@ -35,6 +36,7 @@ export default defineComponent({
|
||||
},
|
||||
sourceControlStoreUnsubscribe: () => {},
|
||||
loading: false,
|
||||
documentTitle: useDocumentTitle(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -86,6 +88,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.documentTitle.set(this.$locale.baseText('credentials.heading'));
|
||||
this.sourceControlStoreUnsubscribe = this.sourceControlStore.$onAction(({ name, after }) => {
|
||||
if (name === 'pullWorkfolder' && after) {
|
||||
after(() => {
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue';
|
||||
import GlobalExecutionsList from '@/components/executions/global/GlobalExecutionsList.vue';
|
||||
import { setPageTitle } from '@/utils/htmlUtils';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useExecutionsStore } from '@/stores/executions.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ExecutionFilterType } from '@/Interface';
|
||||
|
||||
@ -16,6 +16,7 @@ const telemetry = useTelemetry();
|
||||
const externalHooks = useExternalHooks();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const executionsStore = useExecutionsStore();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
@ -32,7 +33,7 @@ onBeforeMount(async () => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
setPageTitle(`n8n - ${i18n.baseText('executionsList.workflowExecutions')}`);
|
||||
documentTitle.set(i18n.baseText('executionsList.workflowExecutions'));
|
||||
document.addEventListener('visibilitychange', onDocumentVisibilityChange);
|
||||
|
||||
await executionsStore.initialize();
|
||||
|
@ -78,7 +78,7 @@ import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
||||
import { useExecutionsStore } from '@/stores/executions.store';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useTitleChange } from '@/composables/useTitleChange';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
@ -122,7 +122,7 @@ const telemetry = useTelemetry();
|
||||
const externalHooks = useExternalHooks();
|
||||
const toast = useToast();
|
||||
const message = useMessage();
|
||||
const { titleReset, titleSet } = useTitleChange();
|
||||
const documentTitle = useDocumentTitle();
|
||||
const workflowHelpers = useWorkflowHelpers({ router });
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
|
||||
@ -371,7 +371,7 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
|
||||
|
||||
async function openWorkflow(data: IWorkflowDb) {
|
||||
resetWorkspace();
|
||||
titleSet(editableWorkflow.value.name, 'IDLE');
|
||||
workflowHelpers.setDocumentTitle(editableWorkflow.value.name, 'IDLE');
|
||||
|
||||
await initializeWorkspace(data);
|
||||
|
||||
@ -1218,7 +1218,7 @@ async function onSourceControlPull() {
|
||||
if (workflowId.value && !uiStore.stateIsDirty) {
|
||||
const workflowData = await workflowsStore.fetchWorkflow(workflowId.value);
|
||||
if (workflowData) {
|
||||
titleSet(workflowData.name, 'IDLE');
|
||||
workflowHelpers.setDocumentTitle(workflowData.name, 'IDLE');
|
||||
await openWorkflow(workflowData);
|
||||
}
|
||||
}
|
||||
@ -1372,7 +1372,7 @@ function checkIfRouteIsAllowed() {
|
||||
|
||||
async function initializeDebugMode() {
|
||||
if (route.name === VIEWS.EXECUTION_DEBUG) {
|
||||
titleSet(workflowsStore.workflowName, 'DEBUG');
|
||||
workflowHelpers.setDocumentTitle(workflowsStore.workflowName, 'DEBUG');
|
||||
|
||||
if (!workflowsStore.isInDebugMode) {
|
||||
await applyExecutionData(route.params.executionId as string);
|
||||
@ -1487,7 +1487,7 @@ onBeforeMount(() => {
|
||||
onMounted(() => {
|
||||
canvasStore.startLoading();
|
||||
|
||||
titleReset();
|
||||
documentTitle.reset();
|
||||
resetWorkspace();
|
||||
|
||||
void initializeData().then(() => {
|
||||
|
@ -46,7 +46,7 @@ import { useGlobalLinkActions } from '@/composables/useGlobalLinkActions';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import useCanvasMouseSelect from '@/composables/useCanvasMouseSelect';
|
||||
import { useExecutionDebugging } from '@/composables/useExecutionDebugging';
|
||||
import { useTitleChange } from '@/composables/useTitleChange';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useDataSchema } from '@/composables/useDataSchema';
|
||||
import { type ContextMenuAction, useContextMenu } from '@/composables/useContextMenu';
|
||||
import { useUniqueNodeName } from '@/composables/useUniqueNodeName';
|
||||
@ -258,7 +258,7 @@ export default defineComponent({
|
||||
callDebounced,
|
||||
...useCanvasMouseSelect(),
|
||||
...useGlobalLinkActions(),
|
||||
...useTitleChange(),
|
||||
documentTitle: useDocumentTitle(),
|
||||
...useToast(),
|
||||
...useMessage(),
|
||||
...useUniqueNodeName(),
|
||||
@ -591,7 +591,7 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
this.canvasStore.initInstance(this.nodeViewRef);
|
||||
this.titleReset();
|
||||
this.documentTitle.reset();
|
||||
|
||||
window.addEventListener('message', this.onPostMessageReceived);
|
||||
|
||||
@ -1719,7 +1719,7 @@ export default defineComponent({
|
||||
this.workflowsStore.executingNode.length = 0;
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
|
||||
this.titleSet(this.workflowsStore.workflowName, 'IDLE');
|
||||
this.workflowHelpers.setDocumentTitle(this.workflowsStore.workflowName, 'IDLE');
|
||||
this.showMessage({
|
||||
title: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.title'),
|
||||
message: this.$locale.baseText(
|
||||
@ -1743,7 +1743,7 @@ export default defineComponent({
|
||||
retryOf: execution.retryOf,
|
||||
};
|
||||
this.workflowsStore.finishActiveExecution(pushData);
|
||||
this.titleSet(execution.workflowData.name, 'IDLE');
|
||||
this.workflowHelpers.setDocumentTitle(execution.workflowData.name, 'IDLE');
|
||||
this.workflowsStore.executingNode.length = 0;
|
||||
this.workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse);
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
@ -3410,7 +3410,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (workflow) {
|
||||
this.titleSet(workflow.name, 'IDLE');
|
||||
this.workflowHelpers.setDocumentTitle(workflow.name, 'IDLE');
|
||||
await this.openWorkflow(workflow);
|
||||
await this.checkAndInitDebugMode();
|
||||
|
||||
@ -4371,7 +4371,7 @@ export default defineComponent({
|
||||
},
|
||||
async checkAndInitDebugMode() {
|
||||
if (this.$route.name === VIEWS.EXECUTION_DEBUG) {
|
||||
this.titleSet(this.workflowName, 'DEBUG');
|
||||
this.workflowHelpers.setDocumentTitle(this.workflowName, 'DEBUG');
|
||||
if (!this.workflowsStore.isInDebugMode) {
|
||||
await this.applyExecutionData(this.$route.params.executionId as string);
|
||||
this.workflowsStore.isInDebugMode = true;
|
||||
@ -4444,7 +4444,7 @@ export default defineComponent({
|
||||
const workflow: IWorkflowDb | undefined =
|
||||
await this.workflowsStore.fetchWorkflow(workflowId);
|
||||
if (workflow) {
|
||||
this.titleSet(workflow.name, 'IDLE');
|
||||
this.workflowHelpers.setDocumentTitle(workflow.name, 'IDLE');
|
||||
await this.openWorkflow(workflow);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import { useRolesStore } from '@/stores/roles.store';
|
||||
import type { ProjectRole } from '@/types/roles.types';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
type FormDataDiff = {
|
||||
name?: string;
|
||||
@ -33,6 +34,7 @@ const cloudPlanStore = useCloudPlanStore();
|
||||
const toast = useToast();
|
||||
const router = useRouter();
|
||||
const telemetry = useTelemetry();
|
||||
const documentTitle = useDocumentTitle();
|
||||
const dialogVisible = ref(false);
|
||||
const upgradeDialogVisible = ref(false);
|
||||
|
||||
@ -250,6 +252,7 @@ onBeforeMount(async () => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
documentTitle.set(locale.baseText('projects.settings'));
|
||||
selectProjectNameIfMatchesDefault();
|
||||
});
|
||||
</script>
|
||||
|
@ -3,6 +3,7 @@ import { defineComponent } from 'vue';
|
||||
import type { ApiKey, IUser } from '@/Interface';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
import CopyInput from '@/components/CopyInput.vue';
|
||||
import { mapStores } from 'pinia';
|
||||
@ -23,6 +24,7 @@ export default defineComponent({
|
||||
...useToast(),
|
||||
...useMessage(),
|
||||
...useUIStore(),
|
||||
documentTitle: useDocumentTitle(),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@ -35,6 +37,7 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.documentTitle.set(this.$locale.baseText('settings.api'));
|
||||
if (!this.isPublicApiEnabled) return;
|
||||
|
||||
void this.getApiKeys();
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
} from '@/constants';
|
||||
import CommunityPackageCard from '@/components/CommunityPackageCard.vue';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import type { PublicInstalledPackage } from 'n8n-workflow';
|
||||
|
||||
import { useCommunityNodesStore } from '@/stores/communityNodes.store';
|
||||
@ -31,6 +32,7 @@ const externalHooks = useExternalHooks();
|
||||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const toast = useToast();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const communityNodesStore = useCommunityNodesStore();
|
||||
const uiStore = useUIStore();
|
||||
@ -85,6 +87,7 @@ onBeforeMount(() => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(i18n.baseText('settings.communityNodes'));
|
||||
try {
|
||||
loading.value = true;
|
||||
await communityNodesStore.fetchInstalledPackages();
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import ExternalSecretsProviderCard from '@/components/ExternalSecretsProviderCard.ee.vue';
|
||||
@ -11,6 +12,7 @@ const i18n = useI18n();
|
||||
const uiStore = useUIStore();
|
||||
const externalSecretsStore = useExternalSecretsStore();
|
||||
const toast = useToast();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const sortedProviders = computed(() => {
|
||||
return ([...externalSecretsStore.providers] as ExternalSecretsProvider[]).sort((a, b) => {
|
||||
@ -19,6 +21,7 @@ const sortedProviders = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
documentTitle.set(i18n.baseText('settings.externalSecrets.title'));
|
||||
if (!externalSecretsStore.isEnterpriseExternalSecretsEnabled) return;
|
||||
try {
|
||||
void externalSecretsStore.fetchAllSecrets();
|
||||
|
@ -5,6 +5,7 @@ import { capitalizeFirstLetter } from '@/utils/htmlUtils';
|
||||
import { convertToDisplayDate } from '@/utils/typesUtils';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import type {
|
||||
ILdapConfig,
|
||||
ILdapSyncData,
|
||||
@ -65,6 +66,7 @@ type CellClassStyleMethodParams<T> = {
|
||||
const toast = useToast();
|
||||
const i18n = useI18n();
|
||||
const message = useMessage();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const uiStore = useUIStore();
|
||||
@ -585,6 +587,7 @@ const reloadLdapSynchronizations = async () => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(i18n.baseText('settings.ldap'));
|
||||
if (!isLDAPFeatureEnabled.value) return;
|
||||
await getLdapConfig();
|
||||
});
|
||||
|
@ -13,6 +13,7 @@ import type { MessageEventBusDestinationOptions } from 'n8n-workflow';
|
||||
import { deepCopy, defaultMessageEventBusDestinationOptions } from 'n8n-workflow';
|
||||
import EventDestinationCard from '@/components/SettingsLogStreaming/EventDestinationCard.ee.vue';
|
||||
import { createEventBus } from 'n8n-design-system/utils';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SettingsLogStreamingView',
|
||||
@ -26,9 +27,11 @@ export default defineComponent({
|
||||
destinations: Array<MessageEventBusDestinationOptions>,
|
||||
disableLicense: false,
|
||||
allDestinations: [] as MessageEventBusDestinationOptions[],
|
||||
documentTitle: useDocumentTitle(),
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.documentTitle.set(this.$locale.baseText('settings.log-streaming.heading'));
|
||||
if (!this.isLicensed) return;
|
||||
|
||||
// Prepare credentialsStore so modals can pick up credentials
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import type { IFormInputs, IUser, ThemeOption } from '@/Interface';
|
||||
import {
|
||||
CHANGE_PASSWORD_MODAL_KEY,
|
||||
@ -28,6 +29,7 @@ type UserBasicDetailsWithMfa = UserBasicDetailsForm & {
|
||||
|
||||
const i18n = useI18n();
|
||||
const { showToast, showError } = useToast();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const hasAnyBasicInfoChanges = ref<boolean>(false);
|
||||
const formInputs = ref<null | IFormInputs>(null);
|
||||
@ -80,6 +82,7 @@ const hasAnyChanges = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
documentTitle.set(i18n.baseText('settings.personal.personalSettings'));
|
||||
formInputs.value = [
|
||||
{
|
||||
name: 'firstName',
|
||||
|
@ -8,6 +8,7 @@ import { useToast } from '@/composables/useToast';
|
||||
import { useLoadingService } from '@/composables/useLoadingService';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import CopyInput from '@/components/CopyInput.vue';
|
||||
import type { TupleToUnion } from '@/utils/typeHelpers';
|
||||
import type { SshKeyTypes } from '@/Interface';
|
||||
@ -17,6 +18,7 @@ const sourceControlStore = useSourceControlStore();
|
||||
const uiStore = useUIStore();
|
||||
const toast = useToast();
|
||||
const message = useMessage();
|
||||
const documentTitle = useDocumentTitle();
|
||||
const loadingService = useLoadingService();
|
||||
|
||||
const isConnected = ref(false);
|
||||
@ -112,6 +114,7 @@ const initialize = async () => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(locale.baseText('settings.sourceControl.title'));
|
||||
if (!sourceControlStore.isEnterpriseSourceControlEnabled) return;
|
||||
await initialize();
|
||||
});
|
||||
|
@ -7,6 +7,7 @@ import { useI18n } from '@/composables/useI18n';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
|
||||
const IdentityProviderSettingsType = {
|
||||
@ -21,6 +22,7 @@ const ssoStore = useSSOStore();
|
||||
const uiStore = useUIStore();
|
||||
const message = useMessage();
|
||||
const toast = useToast();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const ssoActivatedLabel = computed(() =>
|
||||
ssoStore.isSamlLoginEnabled
|
||||
@ -144,6 +146,7 @@ const isToggleSsoDisabled = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(i18n.baseText('settings.sso.title'));
|
||||
if (!ssoStore.isEnterpriseSamlEnabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { telemetry } from '@/plugins/telemetry';
|
||||
import { i18n as locale } from '@/plugins/i18n';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { hasPermission } from '@/utils/rbac/permissions';
|
||||
|
||||
const usageStore = useUsageStore();
|
||||
@ -14,6 +15,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const uiStore = useUIStore();
|
||||
const toast = useToast();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const queryParamCallback = ref<string>(
|
||||
`callback=${encodeURIComponent(`${window.location.origin}${window.location.pathname}`)}`,
|
||||
@ -64,6 +66,7 @@ const onLicenseActivation = async () => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(locale.baseText('settings.usageAndPlan.title'));
|
||||
usageStore.setLoading(true);
|
||||
if (route.query.key) {
|
||||
try {
|
||||
|
@ -12,6 +12,7 @@ import { useClipboard } from '@/composables/useClipboard';
|
||||
import type { UpdateGlobalRolePayload } from '@/api/users';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
const clipboard = useClipboard();
|
||||
const { showToast, showError } = useToast();
|
||||
@ -20,6 +21,7 @@ const settingsStore = useSettingsStore();
|
||||
const uiStore = useUIStore();
|
||||
const usersStore = useUsersStore();
|
||||
const ssoStore = useSSOStore();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
@ -28,6 +30,8 @@ const showUMSetupWarning = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(i18n.baseText('settings.users'));
|
||||
|
||||
if (!showUMSetupWarning.value) {
|
||||
await usersStore.fetchUsers();
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { isFullTemplatesCollection } from '@/utils/templates/typeGuards';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { setPageTitle } from '@/utils/htmlUtils';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
|
||||
const externalHooks = useExternalHooks();
|
||||
@ -25,6 +25,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const loading = ref(true);
|
||||
const notFoundError = ref(false);
|
||||
@ -89,9 +90,9 @@ watch(
|
||||
() => collection.value,
|
||||
() => {
|
||||
if (collection.value && 'full' in collection.value && collection.value.full) {
|
||||
setPageTitle(`n8n - Template collection: ${collection.value.name}`);
|
||||
documentTitle.set(`Template collection: ${collection.value.name}`);
|
||||
} else {
|
||||
setPageTitle('n8n - Templates');
|
||||
documentTitle.set('Templates');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -13,7 +13,6 @@ import type {
|
||||
ITemplatesCategory,
|
||||
} from '@/Interface';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { setPageTitle } from '@/utils/htmlUtils';
|
||||
import { CREATOR_HUB_URL, VIEWS } from '@/constants';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
@ -22,6 +21,7 @@ import { useUIStore } from '@/stores/ui.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useDebounce } from '@/composables/useDebounce';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
interface ISearchEvent {
|
||||
search_string: string;
|
||||
@ -55,6 +55,7 @@ export default defineComponent({
|
||||
return {
|
||||
callDebounced,
|
||||
...useToast(),
|
||||
documentTitle: useDocumentTitle(),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@ -116,7 +117,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
setPageTitle('n8n - Templates');
|
||||
this.documentTitle.set('Templates');
|
||||
await this.loadCategories();
|
||||
void this.loadWorkflowsAndCollections(true);
|
||||
void this.usersStore.showPersonalizationSurvey();
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { setPageTitle } from '@/utils/htmlUtils';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useTemplateWorkflow } from '@/utils/templates/templateActions';
|
||||
@ -8,6 +7,7 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import TemplatesView from './TemplatesView.vue';
|
||||
|
||||
@ -20,6 +20,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const loading = ref(true);
|
||||
const showPreview = ref(true);
|
||||
@ -63,9 +64,9 @@ watch(
|
||||
() => template.value,
|
||||
(newTemplate) => {
|
||||
if (newTemplate) {
|
||||
setPageTitle(`n8n - Template template: ${newTemplate.name}`);
|
||||
documentTitle.set(`Template template: ${newTemplate.name}`);
|
||||
} else {
|
||||
setPageTitle('n8n - Templates');
|
||||
documentTitle.set('Templates');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, onBeforeMount, onBeforeUnmount } from 'vue';
|
||||
import { computed, ref, onBeforeMount, onBeforeUnmount, onMounted } from 'vue';
|
||||
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
@ -9,6 +9,7 @@ import { useI18n } from '@/composables/useI18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
import type { IResource } from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
|
||||
@ -28,6 +29,7 @@ const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
const message = useMessage();
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const documentTitle = useDocumentTitle();
|
||||
let sourceControlStoreUnsubscribe = () => {};
|
||||
|
||||
const layoutRef = ref<InstanceType<typeof ResourcesListLayout> | null>(null);
|
||||
@ -248,6 +250,10 @@ onBeforeMount(() => {
|
||||
onBeforeUnmount(() => {
|
||||
sourceControlStoreUnsubscribe();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
documentTitle.set(i18n.baseText('variables.heading'));
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -17,6 +17,7 @@ import ProjectTabs from '@/components/Projects/ProjectTabs.vue';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import { getResourcePermissions } from '@/permissions';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
|
||||
interface Filters {
|
||||
search: string;
|
||||
@ -49,6 +50,7 @@ const WorkflowsView = defineComponent({
|
||||
} as Filters,
|
||||
sourceControlStoreUnsubscribe: () => {},
|
||||
loading: false,
|
||||
documentTitle: useDocumentTitle(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -149,6 +151,7 @@ const WorkflowsView = defineComponent({
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.documentTitle.set(this.$locale.baseText('workflows.heading'));
|
||||
await this.setFiltersFromQueryString();
|
||||
|
||||
void this.usersStore.showPersonalizationSurvey();
|
||||
|
Loading…
Reference in New Issue
Block a user