feat(api-docs): generate response examples from Go structs; fix SS2022 PSK regen (#4996)

Stop hand-writing OpenAPI response examples, which kept drifting from the real payloads (clients/traffic missing fields, inbounds/list exposing userId which is json:"-", the fictional inbound-443 tag instead of the real in-<port>-<transport> form).

tools/openapigen now emits frontend/src/generated/examples.ts: a per-struct example instance built from type defaults, validate oneof/min bounds, and example: struct tags, with nested-ref expansion and a cycle guard. build-openapi.mjs composes the {success,obj} envelope from it for any endpoint annotated with responseSchema (+ responseSchemaArray for lists); the hand-written response is dropped for those. Service DTOs InboundOption/ApiTokenView/ProbeResultUI are added to the walker.

#4996: client password regeneration now produces a valid Shadowsocks 2022 PSK (correct base64 length per cipher) when an SS2022 inbound is attached, in both the single and bulk client forms; backend surfaces ssMethod on /inbounds/options so the UI can pick the right length.

Also: Swagger UI persists the Authorization token across reloads (persistAuthorization).
This commit is contained in:
MHSanaei
2026-06-06 14:58:15 +02:00
parent 483952cfa0
commit 83799d71b0
22 changed files with 924 additions and 143 deletions
+25 -1
View File
@@ -228,6 +228,21 @@ export default function ClientFormModal({
return ids;
}, [inbounds]);
const ss2022Method = useMemo(() => {
for (const id of form.inboundIds || []) {
const ib = (inbounds || []).find((row) => row.id === id);
const method = ib?.ssMethod;
if (method && method.substring(0, 4) === '2022') return method;
}
return '';
}, [form.inboundIds, inbounds]);
function regeneratePassword() {
update('password', ss2022Method
? RandomUtil.randomShadowsocksPassword(ss2022Method)
: RandomUtil.randomLowerAndNum(16));
}
const showFlow = useMemo(
() => (form.inboundIds || []).some((id) => flowCapableIds.has(id)),
[form.inboundIds, flowCapableIds],
@@ -257,6 +272,15 @@ export default function ClientFormModal({
}
}, [showReverseTag, form.reverseTag]);
useEffect(() => {
if (!ss2022Method) return;
setForm((prev) => (
RandomUtil.isShadowsocks2022Password(prev.password, ss2022Method)
? prev
: { ...prev, password: RandomUtil.randomShadowsocksPassword(ss2022Method) }
));
}, [ss2022Method]);
const inboundOptions = useMemo(
() => (inbounds || [])
.filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || ''))
@@ -433,7 +457,7 @@ export default function ClientFormModal({
<Form.Item label={t('pages.clients.password')}>
<Space.Compact style={{ display: 'flex' }}>
<Input value={form.password} style={{ flex: 1 }} onChange={(e) => update('password', e.target.value)} />
<Button icon={<ReloadOutlined />} onClick={() => update('password', RandomUtil.randomLowerAndNum(16))} />
<Button icon={<ReloadOutlined />} onClick={regeneratePassword} />
</Space.Compact>
</Form.Item>
</Col>