Skip to content

Commit 06d74c0

Browse files
committed
feat: consumer credentials list, add page
1 parent 6d16c58 commit 06d74c0

File tree

16 files changed

+369
-51
lines changed

16 files changed

+369
-51
lines changed

src/apis/credentials.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { API_CREDENTIALS, SKIP_INTERCEPTOR_HEADER } from '@/config/constant';
2+
import { req } from '@/config/req';
3+
import type { APISIXType } from '@/types/schema/apisix';
4+
import type { APISIXListResponse } from '@/types/schema/apisix/type';
5+
import { queryOptions } from '@tanstack/react-query';
6+
7+
type WithUsername = Pick<APISIXType['Consumer'], 'username'>;
8+
export const getCredentialListQueryOptions = (props: WithUsername) => {
9+
const { username } = props;
10+
return queryOptions({
11+
queryKey: ['credentials', username],
12+
queryFn: () =>
13+
req
14+
.get<unknown, APISIXType['RespCredentialList']>(
15+
API_CREDENTIALS(username),
16+
{
17+
headers: {
18+
[SKIP_INTERCEPTOR_HEADER]: ['404'],
19+
},
20+
}
21+
)
22+
.then((v) => v.data)
23+
.catch((e) => {
24+
// 404 means credentials is empty
25+
if (e.response.status === 404) {
26+
const res: APISIXListResponse<APISIXType['Credential']> = {
27+
total: 0,
28+
list: [],
29+
};
30+
return res;
31+
}
32+
throw e;
33+
}),
34+
});
35+
};
36+
37+
export const putCredentialReq = (
38+
data: APISIXType['CredentialPut'] & WithUsername
39+
) => {
40+
const { username, id, ...rest } = data;
41+
return req.put<
42+
APISIXType['CredentialPut'],
43+
APISIXType['RespCredentialDetail']
44+
>(`${API_CREDENTIALS(username)}/${id}`, rest);
45+
};

src/apis/plugins.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ export const getPluginsListQueryOptions = () => {
3838
queryKey: ['plugins-list'],
3939
queryFn: () =>
4040
req
41-
.get<unknown, APISIXType['RespPluginsList']>(API_PLUGINS_LIST)
41+
.get<unknown, APISIXType['RespPluginList']>(API_PLUGINS_LIST)
4242
.then((v) => v.data),
4343
});
4444
};
4545

4646
export const getPluginsListWithSchemaQueryOptions = (
47-
props: APISIXType['PluginsQuery'] & NeedPluginSchema = { schema: 'normal' }
47+
props: APISIXType['PluginsQuery'] & NeedPluginSchema = { schema: 'schema' }
4848
) => {
4949
const { subsystem, schema } = props;
5050
return queryOptions({

src/components/form-slice/FormItemPlugins/PluginCardList.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { PluginCard, type PluginCardProps } from './PluginCard';
1111
import { useTranslation } from 'react-i18next';
1212
import { observer, useLocalObservable } from 'mobx-react-lite';
13-
import { useLayoutEffect } from 'react';
13+
import { useEffect } from 'react';
1414

1515
type PluginCardListSearchProps = Pick<TextInputProps, 'placeholder'> & {
1616
search: string;
@@ -91,9 +91,18 @@ const PluginCardListCore = (props: PluginCardListProps) => {
9191
const { t } = useTranslation();
9292
const combobox = useVirtualizedCombobox();
9393
const optionsOb = useLocalObservable(() => ({
94-
search: search,
95-
plugins: plugins,
96-
mode: mode,
94+
search: '',
95+
plugins: [] as string[],
96+
mode: 'add' as OptionProps['mode'],
97+
setSearch(search: string) {
98+
this.search = search.toLowerCase().trim();
99+
},
100+
setPlugins(plugins: string[]) {
101+
this.plugins = plugins;
102+
},
103+
setMode(mode: PluginCardProps['mode']) {
104+
this.mode = mode;
105+
},
97106
get list() {
98107
const arr = !this.search
99108
? this.plugins
@@ -109,12 +118,9 @@ const PluginCardListCore = (props: PluginCardListProps) => {
109118
},
110119
}));
111120

112-
// handle state and useLocalObservable
113-
useLayoutEffect(() => {
114-
optionsOb.search = search.toLowerCase().trim();
115-
optionsOb.plugins = plugins;
116-
optionsOb.mode = mode;
117-
}, [optionsOb, search, plugins, mode]);
121+
useEffect(() => optionsOb.setPlugins(plugins), [optionsOb, plugins]);
122+
useEffect(() => optionsOb.setSearch(search), [optionsOb, search]);
123+
useEffect(() => optionsOb.setMode(mode), [optionsOb, mode]);
118124

119125
return (
120126
<Combobox store={combobox}>

src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { isEmpty } from 'rambdax';
66
import { FormSubmitBtn } from '@/components/form/Btn';
77
import type { PluginCardListProps } from './PluginCardList';
88
import { observer } from 'mobx-react-lite';
9-
import { useDeepCompareEffect } from 'react-use';
9+
import { useEffect } from 'react';
1010

1111
export type PluginConfig = { name: string; config: object };
1212
export type PluginEditorDrawerProps = Pick<PluginCardListProps, 'mode'> & {
@@ -34,9 +34,9 @@ const PluginEditorDrawerCore = (props: PluginEditorDrawerProps) => {
3434
methods.reset();
3535
};
3636

37-
useDeepCompareEffect(() => {
37+
useEffect(() => {
3838
methods.setValue('config', toConfigStr(config));
39-
}, [config]);
39+
}, [config, methods]);
4040

4141
return (
4242
<Drawer

src/components/form-slice/FormItemPlugins/index.tsx

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,41 @@ import {
1212
} from 'react-hook-form';
1313
import { useTranslation } from 'react-i18next';
1414
import { useEffect, useMemo } from 'react';
15-
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
16-
import {
17-
getPluginSchemaQueryOptions,
18-
getPluginsListQueryOptions,
19-
} from '@/apis/plugins';
2015
import { PluginCardList, PluginCardListSearch } from './PluginCardList';
2116
import { SelectPluginsDrawer } from './SelectPluginsDrawer';
2217
import { difference } from 'rambdax';
23-
import {
24-
PluginEditorDrawer,
25-
type PluginConfig,
26-
type PluginEditorDrawerProps,
27-
} from './PluginEditorDrawer';
18+
import { PluginEditorDrawer, type PluginConfig } from './PluginEditorDrawer';
2819
import { observer, useLocalObservable } from 'mobx-react-lite';
2920
import type { PluginCardProps } from './PluginCard';
21+
import {
22+
getPluginsListWithSchemaQueryOptions,
23+
type NeedPluginSchema,
24+
} from '@/apis/plugins';
25+
import { useSuspenseQuery } from '@tanstack/react-query';
26+
import { useDeepCompareEffect } from 'react-use';
27+
import type { APISIXType } from '@/types/schema/apisix';
28+
import { toJS } from 'mobx';
3029

3130
export type FormItemPluginsProps<T extends FieldValues> = InputWrapperProps &
3231
UseControllerProps<T> & {
3332
onChange?: (value: Record<string, unknown>) => void;
34-
} & Pick<PluginEditorDrawerProps, 'schema'>;
33+
} & Partial<NeedPluginSchema>;
3534

3635
const FormItemPluginsCore = <T extends FieldValues>(
3736
props: FormItemPluginsProps<T>
3837
) => {
39-
const { controllerProps, restProps } = genControllerProps(props, {});
38+
const {
39+
controllerProps,
40+
restProps: { schema = 'schema', ...restProps },
41+
} = genControllerProps(props, {});
4042
const { t } = useTranslation();
4143

4244
const {
4345
field: { value: rawObject, onChange: fOnChange, name: fName, ...restField },
4446
fieldState,
4547
} = useController<T>(controllerProps);
4648
const isView = useMemo(() => restField.disabled, [restField.disabled]);
47-
const pluginsListReq = useSuspenseQuery(getPluginsListQueryOptions());
49+
4850
const pluginsOb = useLocalObservable(() => ({
4951
__map: new Map<string, object>(),
5052
init(obj: Record<string, object>) {
@@ -54,11 +56,21 @@ const FormItemPluginsCore = <T extends FieldValues>(
5456
this.__map.delete(name);
5557
this.save();
5658
},
59+
allPluginNames: [] as string[],
60+
pluginSchemaObj: new Map<string, APISIXType['PluginSchema']>(),
61+
initPlugins(props: {
62+
names: string[];
63+
originObj: Record<string, Record<string, unknown>>;
64+
}) {
65+
const { names, originObj } = props;
66+
this.allPluginNames = names;
67+
this.pluginSchemaObj = new Map(Object.entries(originObj));
68+
},
5769
get selected() {
5870
return Array.from(this.__map.keys());
5971
},
6072
get unSelected() {
61-
return difference(pluginsListReq.data, this.selected);
73+
return difference(this.allPluginNames, this.selected);
6274
},
6375
save() {
6476
const obj = Object.fromEntries(this.__map);
@@ -77,6 +89,11 @@ const FormItemPluginsCore = <T extends FieldValues>(
7789
} as PluginConfig;
7890
this.setEditorOpened(true);
7991
},
92+
get curPluginSchema() {
93+
const d = this.pluginSchemaObj.get(this.curPlugin.name);
94+
if (!d) return {};
95+
return d[schema];
96+
},
8097
editorOpened: false,
8198
setEditorOpened(val: boolean) {
8299
this.editorOpened = val;
@@ -101,14 +118,17 @@ const FormItemPluginsCore = <T extends FieldValues>(
101118
},
102119
}));
103120

104-
const getSchemaReq = useQuery(
105-
getPluginSchemaQueryOptions(pluginsOb.curPlugin.name)
121+
const pluginsListReq = useSuspenseQuery(
122+
getPluginsListWithSchemaQueryOptions({ schema })
106123
);
107124

108125
// init the selected plugins
109126
useEffect(() => {
110127
pluginsOb.init(rawObject);
111128
}, [pluginsOb, rawObject]);
129+
useDeepCompareEffect(() => {
130+
pluginsOb.initPlugins(pluginsListReq.data);
131+
}, [pluginsOb, pluginsListReq.data]);
112132

113133
return (
114134
<InputWrapper error={fieldState.error?.message} {...restProps}>
@@ -122,8 +142,8 @@ const FormItemPluginsCore = <T extends FieldValues>(
122142
<SelectPluginsDrawer
123143
plugins={pluginsOb.unSelected}
124144
opened={pluginsOb.selectPluginsOpened}
125-
onAdd={(name) => pluginsOb.on('add', name)}
126145
setOpened={pluginsOb.setSelectPluginsOpened}
146+
onAdd={(name) => pluginsOb.on('add', name)}
127147
disabled={restField.disabled}
128148
/>
129149
</Group>
@@ -139,7 +159,7 @@ const FormItemPluginsCore = <T extends FieldValues>(
139159
/>
140160
<PluginEditorDrawer
141161
mode={isView ? 'view' : 'edit'}
142-
schema={getSchemaReq.data}
162+
schema={toJS(pluginsOb.curPluginSchema)}
143163
opened={pluginsOb.editorOpened}
144164
onClose={pluginsOb.closeEditor}
145165
plugin={pluginsOb.curPlugin}
@@ -151,3 +171,4 @@ const FormItemPluginsCore = <T extends FieldValues>(
151171
};
152172

153173
export const FormItemPlugins = observer(FormItemPluginsCore);
174+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { FormPartBasic } from './FormPartBasic';
3+
import { FormSectionGeneral } from './FormSectionGeneral';
4+
import { FormSection } from './FormSection';
5+
import { FormItemPlugins } from './FormItemPlugins';
6+
7+
export const FormPartCredential = () => {
8+
const { t } = useTranslation();
9+
return (
10+
<>
11+
<FormSectionGeneral showDate={false} />
12+
<FormPartBasic showName={false} />
13+
<FormSection legend={t('form.plugins.label')}>
14+
<FormItemPlugins name="plugins" schema="consumer_schema" />
15+
</FormSection>
16+
</>
17+
);
18+
};

src/components/form-slice/FormSection/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import {
88
createContext,
99
useContext,
10+
useEffect,
1011
useMemo,
1112
useRef,
1213
type FC,
@@ -15,7 +16,6 @@ import {
1516
} from 'react';
1617
import classes from './style.module.css';
1718
import { APPSHELL_HEADER_HEIGHT } from '@/config/constant';
18-
import { useDeepCompareEffect } from 'react-use';
1919

2020
const SectionDepthCtx = createContext<number>(0);
2121

@@ -66,7 +66,7 @@ export const FormTOCBox = (props: FormTOCBoxProps) => {
6666
const { children, deps } = props;
6767
const reinitializeRef = useRef(() => {});
6868

69-
useDeepCompareEffect(() => {
69+
useEffect(() => {
7070
reinitializeRef.current();
7171
}, [deps]);
7272

src/components/form-slice/FormSectionGeneral.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type FormSectionInfoProps = {
3434
};
3535

3636
export const FormSectionGeneral = (props: FormSectionInfoProps) => {
37-
const { showDate = true, showID = false } = props;
37+
const { showDate = true, showID = true } = props;
3838
const { t } = useTranslation();
3939
return (
4040
<FormSection legend={t('form.general.title')} >

src/components/page/ToAddPageBtn.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ type FilterKeys<T, R extends string> = {
1010
type ToAddPageBtnProps = {
1111
to: keyof FilterKeys<FileRoutesByTo, 'add'>;
1212
label: string;
13-
};
13+
} & Pick<LinkProps, 'params'>;
1414

15-
export const ToAddPageBtn = ({ to, label }: ToAddPageBtnProps) => {
15+
export const ToAddPageBtn = ({ to, params, label }: ToAddPageBtnProps) => {
1616
return (
1717
<RouteLinkBtn
1818
leftSection={<IconPlus />}
1919
size="compact-sm"
2020
variant="gradient"
2121
to={to}
22+
params={params}
2223
>
2324
{label}
2425
</RouteLinkBtn>

src/config/constant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export const API_PLUGINS = '/plugins';
1313
export const API_PLUGINS_LIST = '/plugins/list';
1414
export const API_PLUGIN_METADATA = '/plugin_metadata';
1515
export const API_CONSUMERS = '/consumers';
16+
export const API_CREDENTIALS = (username: string) =>
17+
`${API_CONSUMERS}/${username}/credentials` as const;
1618

1719
export const SKIP_INTERCEPTOR_HEADER = '__dashboard__skipInterceptor';
1820
export const APPSHELL_HEADER_HEIGHT = 60;

0 commit comments

Comments
 (0)