diff --git a/packages/frontend/editor-ui/src/components/Logo/Logo.vue b/packages/frontend/editor-ui/src/components/Logo/Logo.vue
index 6d643de718..4b25eddec7 100644
--- a/packages/frontend/editor-ui/src/components/Logo/Logo.vue
+++ b/packages/frontend/editor-ui/src/components/Logo/Logo.vue
@@ -65,9 +65,7 @@ onMounted(() => {
-
- {{ releaseChannel }}
-
+
{{ releaseChannel }}
@@ -76,6 +74,7 @@ onMounted(() => {
.logoContainer {
display: flex;
justify-content: center;
+ align-items: center;
}
.logoText {
@@ -91,12 +90,11 @@ onMounted(() => {
background-color: var(--color-background-base);
border: 1px solid var(--color-foreground-base);
border-radius: var(--border-radius-base);
- font-size: var(--font-size-4xs);
+ font-size: var(--font-size-3xs);
font-weight: var(--font-weight-bold);
text-transform: capitalize;
line-height: var(--font-line-height-regular);
- height: var(--spacing-s);
- margin: 10px 0 0 3px;
+ margin: 8px 0 0 3px;
}
.authView {
@@ -104,12 +102,18 @@ onMounted(() => {
margin-bottom: var(--spacing-xl);
}
-.sidebar {
+.logo,
+.logoText {
transform: scale(1.3);
}
-.sidebarExpanded .logo {
+.logoText {
margin-left: var(--spacing-xs);
+ margin-right: var(--spacing-3xs);
+}
+
+.sidebarExpanded .logo {
+ margin-left: var(--spacing-3xs);
}
.sidebarCollapsed .logo {
diff --git a/packages/frontend/editor-ui/src/components/Logo/__tests__/__snapshots__/Logo.test.ts.snap b/packages/frontend/editor-ui/src/components/Logo/__tests__/__snapshots__/Logo.test.ts.snap
index 5947e5d0a5..3a05aaf73d 100644
--- a/packages/frontend/editor-ui/src/components/Logo/__tests__/__snapshots__/Logo.test.ts.snap
+++ b/packages/frontend/editor-ui/src/components/Logo/__tests__/__snapshots__/Logo.test.ts.snap
@@ -44,6 +44,6 @@ exports[`Logo > renders the releaseChannelTag for non-stable releaseChannel 1`]
- dev
+ dev
"
`;
diff --git a/packages/frontend/editor-ui/src/components/MainSidebar.test.ts b/packages/frontend/editor-ui/src/components/MainSidebar.test.ts
new file mode 100644
index 0000000000..a10ea3bd65
--- /dev/null
+++ b/packages/frontend/editor-ui/src/components/MainSidebar.test.ts
@@ -0,0 +1,54 @@
+import { reactive } from 'vue';
+import { createComponentRenderer } from '@/__tests__/render';
+import { createTestingPinia } from '@pinia/testing';
+import { type MockedStore, mockedStore } from '@/__tests__/utils';
+import { defaultSettings } from '@/__tests__/defaults';
+import MainSidebar from '@/components/MainSidebar.vue';
+import { useSettingsStore } from '@/stores/settings.store';
+import { useUIStore } from '@/stores/ui.store';
+import { useSourceControlStore } from '@/stores/sourceControl.store';
+
+vi.mock('vue-router', () => ({
+ useRouter: () => ({}),
+ useRoute: () => reactive({}),
+ RouterLink: vi.fn(),
+}));
+
+let renderComponent: ReturnType;
+let settingsStore: MockedStore;
+let uiStore: MockedStore;
+let sourceControlStore: MockedStore;
+
+describe('MainSidebar', () => {
+ beforeEach(() => {
+ renderComponent = createComponentRenderer(MainSidebar, {
+ pinia: createTestingPinia(),
+ });
+ settingsStore = mockedStore(useSettingsStore);
+ uiStore = mockedStore(useUIStore);
+ sourceControlStore = mockedStore(useSourceControlStore);
+
+ settingsStore.settings = defaultSettings;
+ });
+
+ it('renders the sidebar without error', () => {
+ expect(() => renderComponent()).not.toThrow();
+ });
+
+ test.each([
+ [false, true, true],
+ [true, false, false],
+ [true, true, false],
+ [false, false, false],
+ ])(
+ 'should render readonly tooltip when is opened %s and the environment is readonly %s',
+ (sidebarMenuCollapsed, branchReadOnly, shouldRender) => {
+ uiStore.sidebarMenuCollapsed = sidebarMenuCollapsed;
+ sourceControlStore.preferences.branchReadOnly = branchReadOnly;
+
+ const { queryByTestId } = renderComponent();
+
+ expect(queryByTestId('read-only-env-icon') !== null).toBe(shouldRender);
+ },
+ );
+});
diff --git a/packages/frontend/editor-ui/src/components/MainSidebar.vue b/packages/frontend/editor-ui/src/components/MainSidebar.vue
index f354ff5d6c..a5019bd686 100644
--- a/packages/frontend/editor-ui/src/components/MainSidebar.vue
+++ b/packages/frontend/editor-ui/src/components/MainSidebar.vue
@@ -339,7 +339,12 @@ onClickOutside(createBtn as Ref, () => {
-
+
, () => {
-
+
+
+
+
, () => {
diff --git a/packages/frontend/editor-ui/src/components/NodeDetailsView.vue b/packages/frontend/editor-ui/src/components/NodeDetailsView.vue
index 064d52ed7c..792515c76f 100644
--- a/packages/frontend/editor-ui/src/components/NodeDetailsView.vue
+++ b/packages/frontend/editor-ui/src/components/NodeDetailsView.vue
@@ -836,7 +836,7 @@ onBeforeUnmount(() => {
}
.data-display-wrapper {
- height: calc(100% - var(--spacing-l)) !important;
+ height: 100%;
margin-top: var(--spacing-xl) !important;
margin-bottom: var(--spacing-xl) !important;
width: 100%;
diff --git a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
index 7fa297d7aa..df7207286f 100644
--- a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
@@ -911,6 +911,7 @@
"readOnlyEnv.tooltip.link": "More info.",
"readOnlyEnv.cantAdd.workflow": "You can't add new workflows to a protected n8n instance",
"readOnlyEnv.cantAdd.credential": "You can't add new credentials to a protected n8n instance",
+ "readOnlyEnv.cantAdd.project": "You can't add new projects to a protected n8n instance",
"readOnlyEnv.cantAdd.any": "You can't create new workflows or credentials on a protected n8n instance",
"readOnlyEnv.cantEditOrRun": "This workflow can't be edited or run manually because it's on a protected instance",
"mainSidebar.aboutN8n": "About n8n",
diff --git a/packages/frontend/editor-ui/src/stores/projects.store.ts b/packages/frontend/editor-ui/src/stores/projects.store.ts
index ee882e1dff..8370c36bd4 100644
--- a/packages/frontend/editor-ui/src/stores/projects.store.ts
+++ b/packages/frontend/editor-ui/src/stores/projects.store.ts
@@ -15,6 +15,7 @@ import { STORES } from '@/constants';
import { useUsersStore } from '@/stores/users.store';
import { getResourcePermissions } from '@/permissions';
import type { CreateProjectDto, UpdateProjectDto } from '@n8n/api-types';
+import { useSourceControlStore } from '@/stores/sourceControl.store';
export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
const route = useRoute();
@@ -22,6 +23,7 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
const settingsStore = useSettingsStore();
const credentialsStore = useCredentialsStore();
const usersStore = useUsersStore();
+ const sourceControlStore = useSourceControlStore();
const projects = ref([]);
const myProjects = ref([]);
@@ -60,8 +62,9 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
);
const canCreateProjects = computed(
() =>
- hasUnlimitedProjects.value ||
- (isTeamProjectFeatureEnabled.value && !isTeamProjectLimitExceeded.value),
+ (hasUnlimitedProjects.value ||
+ (isTeamProjectFeatureEnabled.value && !isTeamProjectLimitExceeded.value)) &&
+ !sourceControlStore.preferences.branchReadOnly,
);
const hasPermissionToCreateProjects = computed(() =>
hasPermission(['rbac'], { rbac: { scope: 'project:create' } }),