n8n/packages/editor-ui/src/components/VersionControlPushModal.ee.vue

255 lines
6.9 KiB
Vue
Raw Normal View History

feat: Version control mvp (#6271) * implement basic git service * cleanup connected prop * add skeleton of git functions * initial import/export setup * split out export service * refactor and improve export * begin import * more commands and basic import * clean up imports with transactions * work folder import functions * reintroduce versionid * add missing import to pull workfolder * add get-status endpoint * add cleanup to disconnect * add initRepo options * add more checks and cleanup * minor cleanup * refactor prefs * fix server.ts * fix sending deleted files * rename files to ee * add variable override and fix critical cred import bug * fix mkdir race condition * make initRepo default to true * fix front back integration * improve connect flow * add comment to generated ssh key * fix(editor): use useToast composable * fix buttons size * commenting out repo init for now * fix(editor): update UI logic * fix(editor): remove console.log * fix(editor): remove unused ref * adjust endpoints for improved UI * fix(editor): add push and pull buttons * keep or not ssh key * switching file name to id * fix(editor): add success messages, fix save button * fixed faulty diff preventing pull * fix build * fix(editor): adding loader to VC components * removing duplicate exports * improve conflict finding on push pull * manage pull conflict * alternate push pull * fix pull confirmation * fix rm and credential export/import * switch to alternative pull implementation * fix initial commit * fix(editor): subscribing to VC store action to refresh lists * fix(editor): wrap VC store actions with try * feat: add fine-grained file selection for push action * fix: close modal after successful push * fix(editor): VC preferences validation * fix confirm * fix: update endpoint to /get-status * feat: update pull modal override changes message * fix missing wf error * undo * removing connect endpoint * fix(editor): add button titles * fix(editor): cleaning up store action * add version-control/set-read-only protection * fix(editor): adding set branch readonly * fix(editor): remove Push button if branch set to readonly * fix(editor): fix some styles * fix(editor): remove duplicate and delete actions in WF list when branch is readonly * fix: load status before opening selective push modal * fix(editor): extend readonly logic * add cleanup after failed initRepo * fix deleted files crashing get-status * fix n8n-checkbox in staging dialog * fix(editor): fix loading * fix(editor): resize buttons * fix(editor): fix translation * fix(editor): fix copy text size * fix(editor): fix copy text size * fix(editor): add disconnection confirmation * fix(editor): add disconnection confirmation * fix(editor): set large buttons * add public api Pull endpoint * feat: add refresh ssh key * return prefs when new keys are generated * fix(editor): adding readOnly mode to main header * fix(editor): adding readOnly mode to workflow settings * improve credential owner import * add middleware to endpoints * improve public api error/doc * do not create branch if one already exists * update wordings for connect toasts * fix(editor): updating and separating readonly modes * fix(editor): fix readonly mode in WF list * fix(editor): disable elements dragging on canvas in readonly mode (WIP: not working when NodeView page is loaded first) * fix(editor): fix canvas draggables in readonly env * fix(editor): remove unused variables * fix(editor): hide actions in node connections when readonly * fix(editor): hide actions in node connections when readonly * fix(editor): disable Save button when readonly * fix(editor): disable Save settings if no branch is selected * fix(editor): lint fix * fix(editor): update snapshots * fix(editor): replace Loading... text * fix(editor): reset Loading... text * reset branchname on disconnect * fix(editor): adding some translations * fix(editor): fix unit test * fix(editor): fix loading * fix(editor): set settings saved message * fix(editor): update connection flag * fix branchName not returning after connect * temporary (but still breaking) fix for postgres * fix(editor): adding tooltip to Push/Pull buttons when they're collapsed * fix(editor): enabled activator in readonly mode * fix test * fix(editor): disabling new item addition for workflows in readonly mode * fix(editor): modify Pull/Push button tooltips * do not commit empty variables file --------- Co-authored-by: Michael Auerswald <michael.auerswald@gmail.com> Co-authored-by: Romain Minaud <romain.minaud@gmail.com> Co-authored-by: Alex Grozav <alex@grozav.com>
2023-05-31 06:01:57 -07:00
<script lang="ts" setup>
import Modal from './Modal.vue';
import { CREDENTIAL_EDIT_MODAL_KEY, VERSION_CONTROL_PUSH_MODAL_KEY } from '@/constants';
import { computed, onMounted, ref } from 'vue';
import type { PropType } from 'vue';
import type { EventBus } from 'n8n-design-system/utils';
import type { VersionControlAggregatedFile } from '@/Interface';
import { useI18n, useLoadingService, useToast } from '@/composables';
import { useVersionControlStore } from '@/stores/versionControl.store';
import { useUIStore } from '@/stores';
import { useRoute } from 'vue-router/composables';
const props = defineProps({
data: {
type: Object as PropType<{ eventBus: EventBus; status: VersionControlAggregatedFile[] }>,
default: () => ({}),
},
});
const loadingService = useLoadingService();
const uiStore = useUIStore();
const toast = useToast();
const { i18n } = useI18n();
const versionControlStore = useVersionControlStore();
const route = useRoute();
const staged = ref<Record<string, boolean>>({});
const files = ref<VersionControlAggregatedFile[]>(props.data.status || []);
const commitMessage = ref('');
const loading = ref(true);
const context = ref<'workflow' | 'workflows' | 'credentials' | string>('');
const isSubmitDisabled = computed(() => {
return !commitMessage.value || Object.values(staged.value).every((value) => !value);
});
onMounted(async () => {
context.value = getContext();
try {
staged.value = getStagedFilesByContext(files.value);
} catch (error) {
toast.showError(error, i18n.baseText('error'));
} finally {
loading.value = false;
}
});
function getContext() {
if (route.fullPath.startsWith('/workflows')) {
return 'workflows';
} else if (
route.fullPath.startsWith('/credentials') ||
uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].open
) {
return 'credentials';
} else if (route.fullPath.startsWith('/workflow/')) {
return 'workflow';
}
return '';
}
function getStagedFilesByContext(files: VersionControlAggregatedFile[]): Record<string, boolean> {
const stagedFiles: VersionControlAggregatedFile[] = [];
if (context.value === 'workflows') {
stagedFiles.push(...files.filter((file) => file.file.startsWith('workflows')));
} else if (context.value === 'credentials') {
stagedFiles.push(...files.filter((file) => file.file.startsWith('credentials')));
} else if (context.value === 'workflow') {
const workflowId = route.params.name as string;
stagedFiles.push(...files.filter((file) => file.type === 'workflow' && file.id === workflowId));
}
return stagedFiles.reduce<Record<string, boolean>>((acc, file) => {
acc[file.file] = true;
return acc;
}, {});
}
function setStagedStatus(file: VersionControlAggregatedFile, status: boolean) {
staged.value = {
...staged.value,
[file.file]: status,
};
}
function close() {
uiStore.closeModal(VERSION_CONTROL_PUSH_MODAL_KEY);
}
async function commitAndPush() {
const fileNames = files.value.filter((file) => staged.value[file.file]).map((file) => file.file);
loadingService.startLoading(i18n.baseText('settings.versionControl.loading.push'));
close();
try {
await versionControlStore.pushWorkfolder({
commitMessage: commitMessage.value,
fileNames,
});
toast.showToast({
title: i18n.baseText('settings.versionControl.modals.push.success.title'),
message: i18n.baseText('settings.versionControl.modals.push.success.description'),
type: 'success',
});
} catch (error) {
toast.showError(error, i18n.baseText('error'));
} finally {
loadingService.stopLoading();
}
}
</script>
<template>
<Modal
width="812px"
:title="i18n.baseText('settings.versionControl.modals.push.title')"
:eventBus="data.eventBus"
:name="VERSION_CONTROL_PUSH_MODAL_KEY"
>
<template #content>
<div :class="$style.container">
<n8n-text>
{{ i18n.baseText('settings.versionControl.modals.push.description') }}
<span v-if="context">
{{ i18n.baseText(`settings.versionControl.modals.push.description.${context}`) }}
</span>
<n8n-link
:href="i18n.baseText('settings.versionControl.modals.push.description.learnMore.url')"
>
{{ i18n.baseText('settings.versionControl.modals.push.description.learnMore') }}
</n8n-link>
</n8n-text>
<div v-if="files.length > 0">
<n8n-text bold tag="p" class="mt-l mb-2xs">
{{ i18n.baseText('settings.versionControl.modals.push.filesToCommit') }}
</n8n-text>
<n8n-card
v-for="file in files"
:key="file.file"
:class="$style.listItem"
@click="setStagedStatus(file, !staged[file.file])"
>
<div :class="$style.listItemBody">
<n8n-checkbox
:value="staged[file.file]"
:class="$style.listItemCheckbox"
@input="setStagedStatus(file, !staged[file.file])"
/>
<n8n-text bold>
<span v-if="file.status === 'deleted'">
<span v-if="file.type === 'workflow'"> Workflow </span>
<span v-if="file.type === 'credential'"> Credential </span>
Id: {{ file.id }}
</span>
<span v-else>
{{ file.name }}
</span>
</n8n-text>
<n8n-badge :class="$style.listItemStatus">
{{ file.status }}
</n8n-badge>
</div>
</n8n-card>
<n8n-text bold tag="p" class="mt-l mb-2xs">
{{ i18n.baseText('settings.versionControl.modals.push.commitMessage') }}
</n8n-text>
<n8n-input
type="text"
v-model="commitMessage"
:placeholder="
i18n.baseText('settings.versionControl.modals.push.commitMessage.placeholder')
"
/>
</div>
<div v-else-if="!loading">
<n8n-callout class="mt-l">
{{ i18n.baseText('settings.versionControl.modals.push.everythingIsUpToDate') }}
</n8n-callout>
</div>
</div>
</template>
<template #footer>
<div :class="$style.footer">
<n8n-button type="tertiary" class="mr-2xs" @click="close">
{{ i18n.baseText('settings.versionControl.modals.push.buttons.cancel') }}
</n8n-button>
<n8n-button type="primary" :disabled="isSubmitDisabled" @click="commitAndPush">
{{ i18n.baseText('settings.versionControl.modals.push.buttons.save') }}
</n8n-button>
</div>
</template>
</Modal>
</template>
<style module lang="scss">
.container > * {
overflow-wrap: break-word;
}
.actionButtons {
display: flex;
justify-content: flex-end;
align-items: center;
}
.listItem {
margin-top: var(--spacing-2xs);
margin-bottom: var(--spacing-2xs);
cursor: pointer;
transition: border 0.3s ease;
padding: var(--spacing-xs);
&:hover {
border-color: var(--color-foreground-dark);
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
.listItemBody {
display: flex;
flex-direction: row;
align-items: center;
.listItemCheckbox {
display: inline-flex !important;
margin-bottom: 0 !important;
margin-right: var(--spacing-2xs);
}
.listItemStatus {
margin-left: var(--spacing-2xs);
}
}
}
.footer {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
</style>