Skip to content

Commit cdda95b

Browse files
authored
Merge pull request #843 from raheeliftikhar5/user-api-keys
User API Keys
2 parents 345d295 + 040e400 commit cdda95b

File tree

14 files changed

+461
-13
lines changed

14 files changed

+461
-13
lines changed

client/packages/lowcoder/src/api/userApi.ts

+35
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ export interface GetUserResponse extends ApiResponse {
3838
} & BaseUserInfo;
3939
}
4040

41+
export interface ApiKeyPayload {
42+
name: string;
43+
description?: string;
44+
}
45+
46+
export interface FetchApiKeysResponse extends ApiResponse {
47+
data: {
48+
id: string;
49+
name: string;
50+
description: string;
51+
token: string;
52+
}
53+
}
54+
4155
export type GetCurrentUserResponse = GenericApiResponse<CurrentUser>;
4256

4357
class UserApi extends Api {
@@ -55,6 +69,9 @@ class UserApi extends Api {
5569
static markUserStatusURL = "/users/mark-status";
5670
static userDetailURL = (id: string) => `/users/userDetail/${id}`;
5771
static resetPasswordURL = `/users/reset-password`;
72+
static fetchApiKeysURL = `/auth/api-keys`;
73+
static createApiKeyURL = `/auth/api-key`;
74+
static deleteApiKeyURL = (id: string) => `/auth/api-key/${id}`;
5875

5976
static thirdPartyLogin(
6077
request: ThirdPartyAuthRequest & CommonLoginParam
@@ -120,6 +137,24 @@ class UserApi extends Api {
120137
static resetPassword(userId: string): AxiosPromise<ApiResponse> {
121138
return Api.post(UserApi.resetPasswordURL, { userId: userId });
122139
}
140+
141+
static createApiKey({
142+
name,
143+
description = ''
144+
}: ApiKeyPayload): AxiosPromise<ApiResponse> {
145+
return Api.post(UserApi.createApiKeyURL, {
146+
name,
147+
description
148+
});
149+
}
150+
151+
static fetchApiKeys(): AxiosPromise<ApiResponse> {
152+
return Api.get(UserApi.fetchApiKeysURL);
153+
}
154+
155+
static deleteApiKey(apiKeyId: string): AxiosPromise<ApiResponse> {
156+
return Api.delete(UserApi.deleteApiKeyURL(apiKeyId));
157+
}
123158
}
124159

125160
export default UserApi;

client/packages/lowcoder/src/constants/reduxActionConstants.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export const ReduxActionTypes = {
77
FETCH_CURRENT_USER_SUCCESS: "FETCH_CURRENT_USER_SUCCESS",
88
FETCH_RAW_CURRENT_USER: "FETCH_RAW_CURRENT_USER",
99
FETCH_RAW_CURRENT_USER_SUCCESS: "FETCH_RAW_CURRENT_USER_SUCCESS",
10+
FETCH_API_KEYS: "FETCH_API_KEYS",
11+
FETCH_API_KEYS_SUCCESS: "FETCH_API_KEYS_SUCCESS",
12+
1013

1114
/* plugin RELATED */
1215
FETCH_DATA_SOURCE_TYPES: "FETCH_DATA_SOURCE_TYPES",

client/packages/lowcoder/src/constants/userConstants.ts

+7
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,11 @@ export const defaultCurrentUser: CurrentUser = {
8383
extra: {},
8484
};
8585

86+
export type ApiKey = {
87+
id: string;
88+
name: string;
89+
description: string;
90+
token: string;
91+
}
92+
8693
export type UserStatusType = keyof BaseUserInfo["userStatus"];

client/packages/lowcoder/src/i18n/locales/de.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -2261,7 +2261,15 @@ export const de: typeof en = {
22612261
"createdApps": "Erstellte Apps",
22622262
"createdModules": "Erstellte Module",
22632263
"onMarketplace": "Auf dem Marktplatz",
2264-
"howToPublish": "So veröffentlichen Sie auf dem Marktplatz"
2264+
"howToPublish": "So veröffentlichen Sie auf dem Marktplatz",
2265+
"apiKeys": "API-Schlüssel",
2266+
"createApiKey": "API-Schlüssel erstellen",
2267+
"apiKeyName": "Name",
2268+
"apiKeyDescription": "Beschreibung",
2269+
"apiKey": "API-Schlüssel",
2270+
"deleteApiKey": "API-Schlüssel löschen",
2271+
"deleteApiKeyContent": "Sind Sie sicher, dass Sie diesen API-Schlüssel löschen möchten?",
2272+
"deleteApiKeyError": "Etwas ist schief gelaufen. Bitte versuche es erneut."
22652273
},
22662274
"shortcut": {
22672275
...en.shortcut,

client/packages/lowcoder/src/i18n/locales/en.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -2314,7 +2314,15 @@ export const en = {
23142314
"createdApps": "Created Apps",
23152315
"createdModules": "Created Modules",
23162316
"onMarketplace": "On Marketplace",
2317-
"howToPublish": "How to publish on Marketplace"
2317+
"howToPublish": "How to publish on Marketplace",
2318+
"apiKeys": "API Keys",
2319+
"createApiKey": "Create API Key",
2320+
"apiKeyName": "Name",
2321+
"apiKeyDescription": "Description",
2322+
"apiKey": "API Key",
2323+
"deleteApiKey": "Delete API Key",
2324+
"deleteApiKeyContent": "Are you sure you want to delete this API key?",
2325+
"deleteApiKeyError": "Something went wrong. Please try again."
23182326
},
23192327
"shortcut": {
23202328
"shortcutList": "Keyboard Shortcuts",

client/packages/lowcoder/src/i18n/locales/zh.ts

+8
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,14 @@ profile: {
21482148
createdModules: "创建的模块",
21492149
onMarketplace: "在市场上",
21502150
howToPublish: "如何在 Marketplace 上发布",
2151+
apiKeys: "API 密钥",
2152+
createApiKey: "创建 API 密钥",
2153+
apiKeyName: "姓名",
2154+
apiKeyDescription: "描述",
2155+
apiKey: "API密钥",
2156+
deleteApiKey: "删除 API 密钥",
2157+
deleteApiKeyContent: "您确定要删除此 API 密钥吗?",
2158+
deleteApiKeyError: "出了些问题。请再试一次。"
21512159
},
21522160
shortcut: {
21532161
shortcutList: "键盘快捷键",

client/packages/lowcoder/src/pages/ApplicationV2/UserProfileLayout.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import React from 'react';
21
import CountUp from 'react-countup';
32
import { useSelector } from "react-redux";
43
import styled from "styled-components";
@@ -20,7 +19,7 @@ import { ALL_APPLICATIONS_URL } from "constants/routesURL";
2019
import { USER_PROFILE_URL } from "constants/routesURL";
2120
import { default as Divider } from "antd/es/divider";
2221

23-
import { Avatar, Badge, Button, Card, Col, Row, Space, Typography, Select } from 'antd';
22+
import { Avatar, Badge, Button, Card, Col, Row, Space, Typography, Select, Table, Flex } from "antd";
2423

2524
import {
2625
BlurFinishInput,
@@ -40,6 +39,7 @@ import { updateUserAction, updateUserSuccess } from "redux/reduxActions/userActi
4039
import { default as Upload, UploadChangeParam } from "antd/es/upload";
4140
import { USER_HEAD_UPLOAD_URL } from "constants/apiConstants";
4241
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
42+
import UserApiKeysCard from './components/UserApiKeysCard';
4343

4444
const { Text, Title, Link } = Typography;
4545
const { Option } = Select;
@@ -161,7 +161,9 @@ export function UserProfileLayout(props: UserProfileLayoutProps) {
161161
const modules = useSelector(modulesSelector);
162162
const orgUsers = useSelector(getOrgUsers);
163163
const orgGroups = useSelector(getOrgGroups);
164+
164165
const currentOrgId = user.currentOrgId;
166+
165167
const currentOrg = useMemo(
166168
() => user.orgs.find((o) => o.id === currentOrgId),
167169
[user, currentOrgId]
@@ -192,9 +194,6 @@ export function UserProfileLayout(props: UserProfileLayoutProps) {
192194
}, 1000);
193195
};
194196

195-
console.log("App Language", language);
196-
console.log("User Language", currentUser.uiLanguage);
197-
198197
if (!user.currentOrgId) {
199198
return null;
200199
}
@@ -395,7 +394,9 @@ export function UserProfileLayout(props: UserProfileLayoutProps) {
395394

396395
</Space>
397396
</Card>
398-
397+
398+
<UserApiKeysCard />
399+
399400
</ProfileView>
400401
</ContentWrapper>
401402
</Wrapper>
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
import { useSelector } from "react-redux";
1+
import { useDispatch, useSelector } from "react-redux";
22
import { UserProfileLayout } from "./UserProfileLayout";
33
import { getUser } from "../../redux/selectors/usersSelectors";
44
import { trans } from "../../i18n";
55
import { USER_PROFILE_URL } from "constants/routesURL";
6+
import { useEffect } from "react";
7+
import { fetchApiKeysAction } from "redux/reduxActions/userActions";
68

79
export function UserProfileView() {
810

911
const user = useSelector(getUser);
12+
const dispatch = useDispatch();
13+
14+
useEffect(() => {
15+
if (!user.currentOrgId) return;
16+
17+
dispatch(fetchApiKeysAction());
18+
}, []);
1019

1120
if (!user.currentOrgId) {
1221
return null;
1322
}
1423

24+
1525
return <UserProfileLayout breadcrumb={[{ text: trans("home.profile"), path: USER_PROFILE_URL }]}/>;
1626

1727
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { useEffect, useMemo, useState } from "react";
2+
import {
3+
messageInstance,
4+
CustomSelect,
5+
CloseEyeIcon,
6+
CustomModal,
7+
UnderlineCss,
8+
} from "lowcoder-design";
9+
import { trans } from "i18n";
10+
import { default as Form } from "antd/es/form";
11+
import { default as Input } from "antd/es/input";
12+
import { validateResponse } from "api/apiUtils";
13+
import _ from "lodash";
14+
import { styled } from "styled-components";
15+
import UserApi, { ApiKeyPayload } from "api/userApi";
16+
17+
const CustomModalStyled = styled(CustomModal)`
18+
button {
19+
margin-top: 20px;
20+
}
21+
`;
22+
23+
const FormStyled = styled(Form)`
24+
.ant-form-item-control-input-content > input,
25+
.ant-input-password {
26+
&:hover {
27+
border-color: #8b8fa3;
28+
}
29+
30+
&:focus,
31+
&.ant-input-affix-wrapper-focused {
32+
border-color: #3377ff;
33+
}
34+
}
35+
36+
.ant-form-item-label > label {
37+
font-size: 13px;
38+
line-height: 19px;
39+
.has-tip {
40+
${UnderlineCss};
41+
}
42+
}
43+
44+
.ant-input-password-icon.anticon {
45+
color: #8b8fa3;
46+
47+
&:hover {
48+
color: #222;
49+
}
50+
}
51+
52+
&.ant-form-vertical .ant-form-item-label {
53+
padding-bottom: 4px;
54+
}
55+
56+
.ant-form-item-explain-error {
57+
font-size: 12px;
58+
color: #f73131;
59+
line-height: 20px;
60+
}
61+
62+
.ant-form-item-label
63+
> label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
64+
color: #f73131;
65+
}
66+
67+
.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input,
68+
.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover {
69+
border-color: #f73131;
70+
}
71+
72+
.register {
73+
margin: -4px 0 20px 0;
74+
}
75+
76+
.ant-input-prefix {
77+
margin-right: 8px;
78+
svg {
79+
path,
80+
rect:nth-of-type(1) {
81+
stroke: #8b8fa3;
82+
}
83+
rect:nth-of-type(2) {
84+
fill: #8b8fa3;
85+
}
86+
}
87+
}
88+
`;
89+
90+
type CreateApiKeyModalProps = {
91+
modalVisible: boolean;
92+
closeModal: () => void;
93+
onConfigCreate: () => void;
94+
};
95+
96+
function CreateApiKeyModal(props: CreateApiKeyModalProps) {
97+
const {
98+
modalVisible,
99+
closeModal,
100+
onConfigCreate
101+
} = props;
102+
const [form] = Form.useForm();
103+
const [saveLoading, setSaveLoading] = useState(false);
104+
105+
const handleOk = () => {
106+
form.validateFields().then(values => {
107+
// console.log(values)
108+
createApiKey(values)
109+
})
110+
}
111+
function createApiKey(values: ApiKeyPayload) {
112+
setSaveLoading(true);
113+
114+
UserApi.createApiKey(values)
115+
.then((resp) => {
116+
if (validateResponse(resp)) {
117+
messageInstance.success(trans("idSource.saveSuccess"));
118+
}
119+
})
120+
.catch((e) => messageInstance.error(e.message))
121+
.finally(() => {
122+
setSaveLoading(false);
123+
onConfigCreate();
124+
});
125+
}
126+
127+
function handleCancel() {
128+
closeModal();
129+
form.resetFields();
130+
}
131+
132+
return (
133+
<CustomModalStyled
134+
width="500px"
135+
title={"Create API Key"}
136+
open={modalVisible}
137+
okText={"Save"}
138+
okButtonProps={{
139+
loading: saveLoading
140+
}}
141+
onOk={handleOk}
142+
onCancel={handleCancel}
143+
destroyOnClose
144+
afterClose={() => form.resetFields()}
145+
>
146+
<FormStyled
147+
form={form}
148+
name="basic"
149+
layout="vertical"
150+
style={{ maxWidth: 440 }}
151+
autoComplete="off"
152+
>
153+
<Form.Item
154+
name="name"
155+
label="Name"
156+
rules={[{ required: true }]}
157+
>
158+
<Input
159+
placeholder={trans("idSource.formPlaceholder", {
160+
label: 'Name'
161+
})}
162+
/>
163+
</Form.Item>
164+
<Form.Item
165+
name="description"
166+
label="Description"
167+
>
168+
<Input
169+
placeholder={trans("idSource.formPlaceholder", {
170+
label: 'Description'
171+
})}
172+
/>
173+
</Form.Item>
174+
</FormStyled>
175+
</CustomModalStyled>
176+
);
177+
}
178+
179+
export default CreateApiKeyModal;

0 commit comments

Comments
 (0)