diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts
index 738dcc3f6e..1008a09639 100644
--- a/packages/editor-ui/src/Interface.ts
+++ b/packages/editor-ui/src/Interface.ts
@@ -833,6 +833,9 @@ export interface ISettingsState {
promptsData: IN8nPrompts;
userManagement: IUserManagementConfig;
templatesEndpointHealthy: boolean;
+ api: {
+ key: string | undefined;
+ };
}
export interface ITemplateState {
diff --git a/packages/editor-ui/src/api/api-keys.ts b/packages/editor-ui/src/api/api-keys.ts
index 9d0a03660a..27598149be 100644
--- a/packages/editor-ui/src/api/api-keys.ts
+++ b/packages/editor-ui/src/api/api-keys.ts
@@ -6,7 +6,7 @@ export function getApiKey(context: IRestApiContext): Promise<{ apiKey: string |
}
export function createApiKey(context: IRestApiContext): Promise<{ apiKey: string | null }> {
- return makeRestApiRequest(context, 'POST', '/users/me/api-key');
+ return makeRestApiRequest(context, 'POST', '/me/api-key');
}
export function deleteApiKey(context: IRestApiContext): Promise<{ success: boolean }> {
diff --git a/packages/editor-ui/src/components/CopyInput.vue b/packages/editor-ui/src/components/CopyInput.vue
index baad3bb8c9..1837d827b3 100644
--- a/packages/editor-ui/src/components/CopyInput.vue
+++ b/packages/editor-ui/src/components/CopyInput.vue
@@ -2,11 +2,11 @@
-
{{ copyContent }}
-
{{ copyButtonText }}
+
{{ value }}
+
{{ copyButtonText || $locale.baseText('generic.copyToClipboard') }}
-
{{ subtitle }}
+
{{ hint }}
@@ -20,26 +20,29 @@ export default mixins(copyPaste, showMessage).extend({
label: {
type: String,
},
- subtitle: {
+ hint: {
type: String,
},
- copyContent: {
+ value: {
type: String,
},
copyButtonText: {
type: String,
},
- successMessage: {
+ toastTitle: {
+ type: String,
+ },
+ toastMessage: {
type: String,
},
},
methods: {
copy(): void {
- this.copyToClipboard(this.$props.copyContent);
+ this.copyToClipboard(this.value);
this.$showMessage({
- title: this.$locale.baseText('credentialEdit.credentialEdit.showMessage.title'),
- message: this.$props.successMessage,
+ title: this.toastTitle || this.$locale.baseText('generic.copiedToClipboard'),
+ message: this.toastMessage,
type: 'success',
});
},
@@ -54,6 +57,10 @@ export default mixins(copyPaste, showMessage).extend({
font-family: Monaco, Consolas;
line-height: 1.5;
font-size: var(--font-size-s);
+ overflow: hidden;
+ width: 100%;
+ display: block;
+ text-overflow: ellipsis;
}
padding: var(--spacing-xs);
@@ -86,7 +93,7 @@ export default mixins(copyPaste, showMessage).extend({
}
}
-.subtitle {
+.hint {
margin-top: var(--spacing-2xs);
font-size: var(--font-size-2xs);
line-height: var(--font-line-height-loose);
diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue
index 705f5f51b5..d5eb29441f 100644
--- a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue
+++ b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue
@@ -48,10 +48,11 @@
+
+
+
+
+ {{$locale.baseText('settings.api.delete.description')}}
+
+
+
+
+
+
+ {{ $locale.baseText('generic.cancel') }}
+
+
+ {{ $locale.baseText('settings.api.delete.button') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/editor-ui/src/components/Modals.vue b/packages/editor-ui/src/components/Modals.vue
index 9492273b3c..53a6924212 100644
--- a/packages/editor-ui/src/components/Modals.vue
+++ b/packages/editor-ui/src/components/Modals.vue
@@ -88,6 +88,10 @@
+
+
+
+
@@ -95,6 +99,7 @@
import Vue from "vue";
import {
ABOUT_MODAL_KEY,
+ DELETE_API_KEY_MODAL_KEY,
CHANGE_PASSWORD_MODAL_KEY,
CONTACT_PROMPT_MODAL_KEY,
CREDENTIAL_EDIT_MODAL_KEY,
@@ -120,6 +125,7 @@ import CredentialEdit from "./CredentialEdit/CredentialEdit.vue";
import CredentialsList from "./CredentialsList.vue";
import InviteUsersModal from "./InviteUsersModal.vue";
import CredentialsSelectModal from "./CredentialsSelectModal.vue";
+import DeleteApiKeyModal from "./DeleteApiKeyModal.vue";
import DuplicateWorkflowDialog from "./DuplicateWorkflowDialog.vue";
import ModalRoot from "./ModalRoot.vue";
import PersonalizationModal from "./PersonalizationModal.vue";
@@ -142,6 +148,7 @@ export default Vue.extend({
CredentialEdit,
CredentialsList,
CredentialsSelectModal,
+ DeleteApiKeyModal,
DeleteUserModal,
DuplicateWorkflowDialog,
InviteUsersModal,
@@ -155,6 +162,7 @@ export default Vue.extend({
WorkflowOpen,
},
data: () => ({
+ DELETE_API_KEY_MODAL_KEY,
CONTACT_PROMPT_MODAL_KEY,
CREDENTIAL_EDIT_MODAL_KEY,
CREDENTIAL_LIST_MODAL_KEY,
diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts
index 8d7f09e806..d80a3a1289 100644
--- a/packages/editor-ui/src/constants.ts
+++ b/packages/editor-ui/src/constants.ts
@@ -22,6 +22,7 @@ export const CHANGE_PASSWORD_MODAL_KEY = 'changePassword';
export const CREDENTIAL_EDIT_MODAL_KEY = 'editCredential';
export const CREDENTIAL_SELECT_MODAL_KEY = 'selectCredential';
export const DELETE_USER_MODAL_KEY = 'deleteUser';
+export const DELETE_API_KEY_MODAL_KEY = 'deleteApiKey';
export const INVITE_USER_MODAL_KEY = 'inviteUser';
export const DUPLICATE_MODAL_KEY = 'duplicate';
export const TAGS_MANAGER_MODAL_KEY = 'tagsManager';
diff --git a/packages/editor-ui/src/modules/settings.ts b/packages/editor-ui/src/modules/settings.ts
index b25fb137ec..1452d27921 100644
--- a/packages/editor-ui/src/modules/settings.ts
+++ b/packages/editor-ui/src/modules/settings.ts
@@ -12,6 +12,7 @@ import Vue from 'vue';
import { CONTACT_PROMPT_MODAL_KEY, VALUE_SURVEY_MODAL_KEY } from '@/constants';
import { ITelemetrySettings } from 'n8n-workflow';
import { testHealthEndpoint } from '@/api/templates';
+import {createApiKey, deleteApiKey, getApiKey} from "@/api/api-keys";
const module: Module = {
namespaced: true,
@@ -24,6 +25,9 @@ const module: Module = {
smtpSetup: false,
},
templatesEndpointHealthy: false,
+ api: {
+ key: undefined,
+ },
},
getters: {
versionCli(state: ISettingsState) {
@@ -68,6 +72,9 @@ const module: Module = {
templatesHost: (state): string => {
return state.settings.templates.host;
},
+ apiKey: (state): string | undefined => {
+ return state.api.key;
+ },
},
mutations: {
setSettings(state: ISettingsState, settings: IN8nUISettings) {
@@ -85,6 +92,9 @@ const module: Module = {
setTemplatesEndpointHealthy(state: ISettingsState) {
state.templatesEndpointHealthy = true;
},
+ setApiKey(state: ISettingsState, apiKey: string | undefined) {
+ state.api.key = apiKey;
+ },
},
actions: {
async getSettings(context: ActionContext) {
@@ -153,6 +163,18 @@ const module: Module = {
await Promise.race([testHealthEndpoint(context.getters.templatesHost), timeout]);
context.commit('setTemplatesEndpointHealthy', true);
},
+ async getApiKey(context: ActionContext) {
+ const { apiKey } = await getApiKey(context.rootGetters['getRestApiContext']);
+ context.commit('setApiKey', apiKey);
+ },
+ async createApiKey(context: ActionContext) {
+ const { apiKey } = await createApiKey(context.rootGetters['getRestApiContext']);
+ context.commit('setApiKey', apiKey);
+ },
+ async deleteApiKey(context: ActionContext) {
+ await deleteApiKey(context.rootGetters['getRestApiContext']);
+ context.commit('setApiKey', undefined);
+ },
},
};
diff --git a/packages/editor-ui/src/modules/ui.ts b/packages/editor-ui/src/modules/ui.ts
index 6342553d6f..cb63f3eaa8 100644
--- a/packages/editor-ui/src/modules/ui.ts
+++ b/packages/editor-ui/src/modules/ui.ts
@@ -1,5 +1,6 @@
import {
ABOUT_MODAL_KEY,
+ DELETE_API_KEY_MODAL_KEY,
CREDENTIAL_EDIT_MODAL_KEY,
CREDENTIAL_SELECT_MODAL_KEY,
CHANGE_PASSWORD_MODAL_KEY,
@@ -32,6 +33,9 @@ const module: Module = {
[ABOUT_MODAL_KEY]: {
open: false,
},
+ [DELETE_API_KEY_MODAL_KEY]: {
+ open: false,
+ },
[CHANGE_PASSWORD_MODAL_KEY]: {
open: false,
},
@@ -145,6 +149,9 @@ const module: Module = {
},
},
actions: {
+ closeModal: async (context: ActionContext, modalKey: string) => {
+ context.commit('closeModal', modalKey);
+ },
openModal: async (context: ActionContext, modalKey: string) => {
context.commit('openModal', modalKey);
},
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index 352b189b12..1f52788f37 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -4,6 +4,12 @@
"_reusableBaseText.save": "Save",
"_reusableDynamicText.oauth2.clientId": "Client ID",
"_reusableDynamicText.oauth2.clientSecret": "Client Secret",
+ "generic.learnMore": "Learn more",
+ "generic.confirm": "Confirm",
+ "generic.cancel": "Cancel",
+ "generic.delete": "Delete",
+ "generic.copyToClipboard": "Copy to clipboard",
+ "generic.copiedToClipboard": "Copied to clipboard",
"about.aboutN8n": "About n8n",
"about.close": "Close",
"about.license": "License",
@@ -653,12 +659,16 @@
"settings.api.create.description.link": "REST API",
"settings.api.create.button": "Create an API Key",
"settings.api.create.button.loading": "Creating API Key...",
- "settings.api.error.title": "Something went wrong",
- "settings.api.error.get": "Could not check if an api key already exists.",
- "settings.api.error.create": "Creating the API Key failed.",
- "settings.api.error.delete": "Deleting the API Key failed.",
+ "settings.api.create.error": "Creating the API Key failed.",
+ "settings.api.delete.title": "Delete this API Key?",
+ "settings.api.delete.description": "Any application using this API Key will no longer be granted access to n8n data. This operation cannot be undone.",
+ "settings.api.delete.button": "Delete Forever",
+ "settings.api.delete.error": "Deleting the API Key failed.",
+ "settings.api.delete.toast": "API Key deleted",
"settings.api.view.info": "Use your API Key to access n8n's REST API to build your own integrations.",
"settings.api.view.myKey": "My API Key",
+ "settings.api.view.copy.toast": "API Key copied to clipboard",
+ "settings.api.view.error": "Could not check if an api key already exists.",
"settings.version": "Version",
"showMessage.cancel": "@:_reusableBaseText.cancel",
"showMessage.ok": "OK",
diff --git a/packages/editor-ui/src/views/SettingsApiView.vue b/packages/editor-ui/src/views/SettingsApiView.vue
index 750b77d1af..109622348f 100644
--- a/packages/editor-ui/src/views/SettingsApiView.vue
+++ b/packages/editor-ui/src/views/SettingsApiView.vue
@@ -6,18 +6,41 @@
{{ $locale.baseText('settings.api') }}
-
- Hello
-
+
+
+
+
+
+ {{ $locale.baseText('settings.api.view.info') }}
+
+ {{ $locale.baseText('generic.learnMore') }}
+
+
+
+
+
+
+ {{ $locale.baseText('generic.delete') }}
+
+
+
+
+
{{ $locale.baseText('settings.api.create.title') }}
- {{$locale.baseText('settings.api.create.description')}}
-
- {{$locale.baseText('settings.api.create.description.link')}}
-
+
+ {{$locale.baseText('settings.api.create.description')}}
+
+ {{$locale.baseText('settings.api.create.description.link')}}
+
+
{{$locale.baseText(loading ? 'settings.api.create.button.loading' : 'settings.api.create.button')}}
@@ -34,6 +57,8 @@ import { IUser } from '@/Interface';
import mixins from 'vue-typed-mixins';
import SettingsView from './SettingsView.vue';
+import CopyInput from '../components/CopyInput.vue';
+import {DELETE_API_KEY_MODAL_KEY} from "../constants";
export default mixins(
showMessage,
@@ -41,30 +66,35 @@ export default mixins(
name: 'SettingsPersonalView',
components: {
SettingsView,
+ CopyInput,
},
data() {
return {
- apiKey: '' as string | null,
loading: false,
mounted: false,
error: '',
};
},
- async mounted() {
+ mounted() {
this.getApiKey();
},
computed: {
currentUser() {
return this.$store.getters['users/currentUser'] as IUser;
},
+ apiKey() {
+ return this.$store.getters['settings/apiKey'];
+ },
},
methods: {
+ showDeleteModal() {
+ this.$store.dispatch('ui/openModal', DELETE_API_KEY_MODAL_KEY);
+ },
async getApiKey() {
try {
- const { apiKey } = await getApiKey(this.$store.getters['getRestApiContext']);
- this.apiKey = apiKey;
+ this.$store.dispatch('settings/getApiKey');
} catch (error) {
- this.$showError(error, this.$locale.baseText('settings.api.error.get'));
+ this.$showError(error, this.$locale.baseText('settings.api.view.error'));
} finally {
this.mounted = true;
}
@@ -73,23 +103,13 @@ export default mixins(
this.loading = true;
try {
- const { apiKey } = await createApiKey(this.$store.getters['getRestApiContext']);
- this.apiKey = apiKey;
+ this.$store.dispatch('settings/createApiKey');
} catch (error) {
- this.$showError(error, this.$locale.baseText('settings.api.error.create'));
+ this.$showError(error, this.$locale.baseText('settings.api.create.error'));
} finally {
this.loading = false;
}
},
- async deleteApiKey() {
- this.loading = true;
-
- try {
- await deleteApiKey(this.$store.getters['getRestApiContext']);
- } catch (error) {
- this.$showError(error, this.$locale.baseText('settings.api.error.delete'));
- }
- },
},
});
@@ -120,5 +140,16 @@ export default mixins(
align-items: center;
flex-direction: column;
}
+
+.card {
+ position: relative;
+}
+
+.delete {
+ position: absolute;
+ display: inline-block;
+ top: var(--spacing-s);
+ right: var(--spacing-s);
+}