3x-ui/frontend/src/pages/settings/SecurityTab.css

85 lines
1.3 KiB
CSS
Raw Normal View History

refactor(frontend): port settings to react+ts Step 5 of the planned vue->react migration. Settings is the first entry whose state model didn't translate to the Vue-style "parent passes a reactive object, children mutate it in place" pattern, so the React port flips it to lifted state + a typed updateSetting patch function. * models/setting.ts — typed AllSetting class with the same field defaults and equals() behavior the vue version had. The .js twin is deleted; nothing else imported it. * hooks/useAllSetting.ts — owns allSetting + oldAllSetting state, exposes updateSetting(patch), saveDisabled is derived via useMemo off equals() (no more 1Hz dirty-check timer). * components/SettingListItem.tsx — children-based wrapper instead of named slots. The vue twin stays alive because xray (BasicsTab, DnsTab) still imports it; deleted when xray migrates. The five tab components and the TwoFactorModal each accept { allSetting, updateSetting } and render with AntD v5's Collapse items[] API. Every v-model:value="x" became value={...} onChange={(e) => updateSetting({ key: e.target.value })} or onChange={(v) => updateSetting({ key: v })} for non-input controls. SubscriptionFormatsTab is the trickiest — fragment / noises[] / mux / direct routing rules are stored as JSON-encoded strings on the wire. Parsing them once via useMemo per field, mutating the parsed object on edit, and stringifying back into the patch keeps the round-trip identical to the vue version. SettingsPage hosts the tab navigation (with hash sync), the save / restart action bar, the security-warnings alert banner, and the restart flow that rebuilds the panel URL after the new host/port/cert settings take effect.
2026-05-21 19:48:15 +00:00
.api-token-section {
padding: 8px 20px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.api-token-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.api-token-hint {
margin: 0;
font-size: 12.5px;
opacity: 0.7;
flex: 1;
min-width: 200px;
}
.api-token-row {
border: 1px solid rgba(128, 128, 128, 0.18);
border-radius: 8px;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 8px;
transition: opacity 0.15s;
}
.api-token-row.disabled {
opacity: 0.55;
}
.api-token-row-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
.api-token-name-wrap {
display: flex;
flex-direction: column;
gap: 2px;
}
.api-token-name {
font-weight: 600;
font-size: 13.5px;
}
.api-token-created {
font-size: 11px;
opacity: 0.55;
}
.api-token-actions {
display: flex;
align-items: center;
gap: 8px;
}
.api-token-value-wrap {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.api-token-value {
flex: 1;
min-width: 0;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12.5px;
padding: 4px 8px;
background: rgba(128, 128, 128, 0.08);
border-radius: 4px;
word-break: break-all;
}