Skip to content

Commit 02b6d43

Browse files
authored
feat: secrets pages (#3019)
1 parent 6b0e473 commit 02b6d43

File tree

12 files changed

+711
-8
lines changed

12 files changed

+711
-8
lines changed

src/apis/secrets.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { API_SECRETS } from '@/config/constant';
2+
import { req } from '@/config/req';
3+
import type { APISIXType } from '@/types/schema/apisix';
4+
import type { PageSearchType } from '@/types/schema/pageSearch';
5+
import { queryOptions } from '@tanstack/react-query';
6+
7+
/**
8+
* `manager` does not exist in apisix secret, we parse it from `id`
9+
* `id` (origin) is `manager/id`, we convert it to `id` and `manager`
10+
*/
11+
export const preParseSecretItem = <T extends APISIXType['RespSecretItem']>(
12+
data: T
13+
) => {
14+
const { id } = data.value;
15+
if (!id) return data;
16+
type IDTuple = [APISIXType['Secret']['manager'], string];
17+
const idTuple = id.split('/') as IDTuple;
18+
if (idTuple.length !== 2) return data;
19+
const [manager, realId] = idTuple;
20+
return { ...data, value: { ...data.value, manager, id: realId } };
21+
};
22+
23+
export const getSecretListQueryOptions = (props: PageSearchType) => {
24+
const { page, pageSize } = props;
25+
return queryOptions({
26+
queryKey: ['secrets', page, pageSize],
27+
queryFn: () =>
28+
req
29+
.get<unknown, APISIXType['RespSecretList']>(API_SECRETS, {
30+
params: { page, page_size: pageSize },
31+
})
32+
.then((v) => {
33+
const { list, ...rest } = v.data;
34+
return {
35+
...rest,
36+
list: list.map(preParseSecretItem),
37+
};
38+
}),
39+
});
40+
};
41+
42+
export const getSecretQueryOptions = (
43+
props: Pick<APISIXType['Secret'], 'id' | 'manager'>
44+
) => {
45+
const { id, manager } = props;
46+
return queryOptions({
47+
queryKey: ['secret', manager, id],
48+
queryFn: () =>
49+
req
50+
.get<unknown, APISIXType['RespSecretDetail']>(
51+
`${API_SECRETS}/${manager}/${id}`
52+
)
53+
.then((v) => preParseSecretItem(v.data)),
54+
});
55+
};
56+
57+
export const putSecretReq = (data: APISIXType['Secret']) => {
58+
const { manager, id, ...rest } = data;
59+
return req.put<APISIXType['Secret'], APISIXType['RespSecretDetail']>(
60+
`${API_SECRETS}/${manager}/${id}`,
61+
rest
62+
);
63+
};
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { useFormContext } from 'react-hook-form';
2+
import { APISIX, type APISIXType } from '@/types/schema/apisix';
3+
import { FormItemSwitch } from '@/components/form/Switch';
4+
import { FormItemTextInput } from '@/components/form/TextInput';
5+
import { useTranslation } from 'react-i18next';
6+
import { FormSection } from './FormSection';
7+
import { FormItemSelect } from '@/components/form/Select';
8+
import { Divider, InputWrapper } from '@mantine/core';
9+
import { FormItemTagsInput } from '../form/TagInput';
10+
11+
const VaultSecretForm = () => {
12+
const { t } = useTranslation();
13+
const { control } = useFormContext<APISIXType['Secret']>();
14+
15+
return (
16+
<>
17+
<FormItemTextInput
18+
control={control}
19+
name="uri"
20+
label={t('form.secrets.vault.uri')}
21+
/>
22+
<FormItemTextInput
23+
control={control}
24+
name="prefix"
25+
label={t('form.secrets.vault.prefix')}
26+
/>
27+
<FormItemTextInput
28+
control={control}
29+
name="token"
30+
label={t('form.secrets.vault.token')}
31+
/>
32+
<FormItemTextInput
33+
control={control}
34+
name="namespace"
35+
label={t('form.secrets.vault.namespace')}
36+
/>
37+
</>
38+
);
39+
};
40+
41+
const AWSSecretForm = () => {
42+
const { t } = useTranslation();
43+
const { control } = useFormContext<APISIXType['Secret']>();
44+
45+
return (
46+
<>
47+
<FormItemTextInput
48+
control={control}
49+
name="access_key_id"
50+
label={t('form.secrets.aws.access_key_id')}
51+
/>
52+
<FormItemTextInput
53+
control={control}
54+
name="secret_access_key"
55+
label={t('form.secrets.aws.secret_access_key')}
56+
/>
57+
<FormItemTextInput
58+
control={control}
59+
name="session_token"
60+
label={t('form.secrets.aws.session_token')}
61+
/>
62+
63+
<FormItemTextInput
64+
control={control}
65+
name="region"
66+
label={t('form.secrets.aws.region')}
67+
/>
68+
<FormItemTextInput
69+
control={control}
70+
name="endpoint_url"
71+
label={t('form.secrets.aws.endpoint_url')}
72+
/>
73+
</>
74+
);
75+
};
76+
77+
const GCPSecretForm = () => {
78+
const { t } = useTranslation();
79+
const { control } = useFormContext<APISIXType['Secret']>();
80+
81+
return (
82+
<>
83+
<InputWrapper label={t('form.secrets.gcp.ssl_verify')}>
84+
<FormItemSwitch control={control} name="ssl_verify" />
85+
</InputWrapper>
86+
<FormSection legend={t('form.secrets.gcp.auth')}>
87+
<FormItemTextInput
88+
control={control}
89+
name="auth_file"
90+
label={t('form.secrets.gcp.auth_file')}
91+
/>
92+
<Divider my="xs" label={t('or')} />
93+
<FormSection legend={t('form.secrets.gcp.auth_config')}>
94+
<FormItemTextInput
95+
control={control}
96+
name="auth_config.client_email"
97+
label={t('form.secrets.gcp.client_email')}
98+
/>
99+
<FormItemTextInput
100+
control={control}
101+
name="auth_config.private_key"
102+
label={t('form.secrets.gcp.private_key')}
103+
/>
104+
<FormItemTextInput
105+
control={control}
106+
name="auth_config.project_id"
107+
label={t('form.secrets.gcp.project_id')}
108+
/>
109+
<FormItemTextInput
110+
control={control}
111+
name="auth_config.token_uri"
112+
label={t('form.secrets.gcp.token_uri')}
113+
/>
114+
<FormItemTagsInput
115+
control={control}
116+
name="auth_config.scope"
117+
label={t('form.secrets.gcp.scope')}
118+
/>
119+
<FormItemTextInput
120+
control={control}
121+
name="auth_config.entries_uri"
122+
label={t('form.secrets.gcp.entries_uri')}
123+
/>
124+
</FormSection>
125+
</FormSection>
126+
</>
127+
);
128+
};
129+
130+
type FormSectionManagerProps = { readOnlyManager?: boolean };
131+
const FormSectionManager = (props: FormSectionManagerProps) => {
132+
const { readOnlyManager } = props;
133+
const { t } = useTranslation();
134+
const { control } = useFormContext<APISIXType['Secret']>();
135+
return (
136+
<FormSection legend={t('form.secrets.manager')} disabled={readOnlyManager}>
137+
<FormItemSelect
138+
control={control}
139+
name="manager"
140+
defaultValue={APISIX.Secret.options[0].shape.manager.value}
141+
data={APISIX.Secret.options.map((v) => v.shape.manager.value)}
142+
/>
143+
</FormSection>
144+
);
145+
};
146+
147+
const FormSectionManagerConfig = () => {
148+
const { t } = useTranslation();
149+
const { watch } = useFormContext<APISIXType['Secret']>();
150+
// useWatch not working here
151+
const manager = watch('manager');
152+
return (
153+
<FormSection legend={t('form.secrets.managerConfig')}>
154+
{manager === 'vault' && <VaultSecretForm />}
155+
{manager === 'aws' && <AWSSecretForm />}
156+
{manager === 'gcp' && <GCPSecretForm />}
157+
</FormSection>
158+
);
159+
};
160+
161+
/**
162+
* id and manager cannot be changed when editing
163+
*/
164+
export const FormPartSecret = (props: FormSectionManagerProps) => {
165+
const { readOnlyManager } = props;
166+
return (
167+
<>
168+
<FormSectionManager readOnlyManager={readOnlyManager} />
169+
<FormSectionManagerConfig />
170+
</>
171+
);
172+
};

src/components/form-slice/FormSectionGeneral.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ const DisplayDate = () => {
2222
const FormItemID = () => {
2323
const { control } = useFormContext<APISIXType['Info']>();
2424
const { t } = useTranslation();
25-
2625
return (
2726
<FormItemTextInput control={control} name="id" label={t('form.info.id')} />
2827
);
@@ -31,13 +30,18 @@ const FormItemID = () => {
3130
export type FormSectionGeneralProps = {
3231
showDate?: boolean;
3332
showID?: boolean;
33+
readOnly?: boolean;
3434
};
3535

3636
export const FormSectionGeneral = (props: FormSectionGeneralProps) => {
37-
const { showDate = true, showID = true } = props;
37+
const { showDate = true, showID = true, readOnly = false } = props;
3838
const { t } = useTranslation();
39+
// we use fieldset disabled to show readonly state
40+
// because mantine readOnly style looks like we can edit
41+
// this is also the way rhf recommends,
42+
// Using disable directly on the component will cause rhf to ignore the value
3943
return (
40-
<FormSection legend={t('form.general.title')}>
44+
<FormSection legend={t('form.general.title')} disabled={readOnly}>
4145
{showID && <FormItemID />}
4246
{showID && showDate && <Divider my="lg" />}
4347
{showDate && <DisplayDate />}

src/config/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const API_GLOBAL_RULES = '/global_rules';
1212
export const API_PLUGINS = '/plugins';
1313
export const API_PLUGINS_LIST = '/plugins/list';
1414
export const API_PLUGIN_METADATA = '/plugin_metadata';
15+
export const API_SECRETS = '/secrets';
1516
export const API_CONSUMERS = '/consumers';
1617
export const API_CREDENTIALS = (username: string) =>
1718
`${API_CONSUMERS}/${username}/credentials` as const;

src/locales/en/common.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,36 @@
9797
"vars": "Vars"
9898
},
9999
"search": "Search",
100+
"secrets": {
101+
"aws": {
102+
"access_key_id": "Access Key ID",
103+
"endpoint_url": "Endpoint URL",
104+
"region": "Region",
105+
"secret_access_key": "Secret Access Key",
106+
"session_token": "Session Token"
107+
},
108+
"gcp": {
109+
"auth": "Auth",
110+
"auth_config": "Auth Configuration",
111+
"auth_file": "Auth File",
112+
"client_email": "Client Email",
113+
"private_key": "Private Key",
114+
"project_id": "Project ID",
115+
"token_uri": "Token URI",
116+
"scope": "Scope",
117+
"entries_uri": "Entries URI",
118+
"ssl_verify": "SSL Verify"
119+
},
120+
"manager": "Secret Manager",
121+
"managerConfig": "Manager Configuration",
122+
"title": "Secret Configuration",
123+
"vault": {
124+
"namespace": "Namespace",
125+
"prefix": "Prefix",
126+
"token": "Token",
127+
"uri": "URI"
128+
}
129+
},
100130
"ssls": {
101131
"cert": "Certificate",
102132
"cert_key_list": {
@@ -348,6 +378,19 @@
348378
"title": "Routes"
349379
},
350380
"seconds": "Seconds",
381+
"secrets": {
382+
"add": {
383+
"success": "Add Secret Successfully",
384+
"title": "Add Secret"
385+
},
386+
"detail": {
387+
"title": "Secret Detail"
388+
},
389+
"edit": {
390+
"success": "Edit Secret Successfully",
391+
"title": "Edit Secret"
392+
}
393+
},
351394
"services": {
352395
"add": {
353396
"success": "Add Service Successfully",

0 commit comments

Comments
 (0)