mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-21 10:27:14 +00:00
refactor(frontend): move form-item hints from extra to tooltip
Switch reality target, node options, and WARP auto-update-IP hints from inline extra text to label tooltips for a cleaner form layout.
This commit is contained in:
@@ -47,7 +47,7 @@ export default function RealityForm({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('pages.inbounds.form.target')}
|
label={t('pages.inbounds.form.target')}
|
||||||
extra={t('pages.inbounds.form.realityTargetHint')}
|
tooltip={t('pages.inbounds.form.realityTargetHint')}
|
||||||
>
|
>
|
||||||
<Space.Compact block>
|
<Space.Compact block>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ export default function NodeFormModal({
|
|||||||
label={t('pages.nodes.allowPrivateAddress')}
|
label={t('pages.nodes.allowPrivateAddress')}
|
||||||
name="allowPrivateAddress"
|
name="allowPrivateAddress"
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
extra={t('pages.nodes.allowPrivateAddressHint')}
|
tooltip={t('pages.nodes.allowPrivateAddressHint')}
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -331,7 +331,7 @@ export default function NodeFormModal({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('pages.nodes.tlsVerifyMode')}
|
label={t('pages.nodes.tlsVerifyMode')}
|
||||||
name="tlsVerifyMode"
|
name="tlsVerifyMode"
|
||||||
extra={t('pages.nodes.tlsVerifyModeHint')}
|
tooltip={t('pages.nodes.tlsVerifyModeHint')}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
disabled={scheme === 'http'}
|
disabled={scheme === 'http'}
|
||||||
@@ -366,7 +366,7 @@ export default function NodeFormModal({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('pages.nodes.pinnedCert')}
|
label={t('pages.nodes.pinnedCert')}
|
||||||
name="pinnedCertSha256"
|
name="pinnedCertSha256"
|
||||||
extra={t('pages.nodes.pinnedCertHint')}
|
tooltip={t('pages.nodes.pinnedCertHint')}
|
||||||
>
|
>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t('pages.nodes.pinnedCertPlaceholder')}
|
placeholder={t('pages.nodes.pinnedCertPlaceholder')}
|
||||||
@@ -381,7 +381,7 @@ export default function NodeFormModal({
|
|||||||
label={t('pages.nodes.apiToken')}
|
label={t('pages.nodes.apiToken')}
|
||||||
name="apiToken"
|
name="apiToken"
|
||||||
rules={[antdRule(NodeFormSchema.shape.apiToken, t)]}
|
rules={[antdRule(NodeFormSchema.shape.apiToken, t)]}
|
||||||
extra={t('pages.nodes.apiTokenHint')}
|
tooltip={t('pages.nodes.apiTokenHint')}
|
||||||
>
|
>
|
||||||
<Input.Password placeholder={t('pages.nodes.apiTokenPlaceholder')} />
|
<Input.Password placeholder={t('pages.nodes.apiTokenPlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -389,7 +389,7 @@ export default function NodeFormModal({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('pages.nodes.outboundTag')}
|
label={t('pages.nodes.outboundTag')}
|
||||||
name="outboundTag"
|
name="outboundTag"
|
||||||
extra={t('pages.nodes.outboundTagHint')}
|
tooltip={t('pages.nodes.outboundTagHint')}
|
||||||
getValueProps={(v) => ({ value: (v as string) || undefined })}
|
getValueProps={(v) => ({ value: (v as string) || undefined })}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
@@ -403,7 +403,7 @@ export default function NodeFormModal({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('pages.nodes.inboundSyncMode')}
|
label={t('pages.nodes.inboundSyncMode')}
|
||||||
name="inboundSyncMode"
|
name="inboundSyncMode"
|
||||||
extra={t('pages.nodes.inboundSyncModeHint')}
|
tooltip={t('pages.nodes.inboundSyncModeHint')}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
options={[
|
options={[
|
||||||
@@ -417,7 +417,7 @@ export default function NodeFormModal({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('pages.nodes.inboundTags')}
|
label={t('pages.nodes.inboundTags')}
|
||||||
name="inboundTags"
|
name="inboundTags"
|
||||||
extra={t('pages.nodes.inboundTagsHint')}
|
tooltip={t('pages.nodes.inboundTagsHint')}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
|
|||||||
@@ -266,170 +266,171 @@ export default function WarpModal({
|
|||||||
<>
|
<>
|
||||||
{messageContextHolder}
|
{messageContextHolder}
|
||||||
<Modal open={open} title="Cloudflare WARP" footer={null} onCancel={onClose}>
|
<Modal open={open} title="Cloudflare WARP" footer={null} onCancel={onClose}>
|
||||||
{!hasWarp ? (
|
{!hasWarp ? (
|
||||||
<Button type="primary" loading={loading} icon={<ApiOutlined />} onClick={register}>
|
<Button type="primary" loading={loading} icon={<ApiOutlined />} onClick={register}>
|
||||||
{t('pages.xray.warp.createAccount')}
|
{t('pages.xray.warp.createAccount')}
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<table className="warp-data-table">
|
|
||||||
<tbody>
|
|
||||||
<tr className="row-odd">
|
|
||||||
<td>{t('pages.xray.warp.accessToken')}</td>
|
|
||||||
<td>{warpData?.access_token}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t('pages.xray.warp.deviceId')}</td>
|
|
||||||
<td>{warpData?.device_id}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="row-odd">
|
|
||||||
<td>{t('pages.xray.warp.licenseKey')}</td>
|
|
||||||
<td>{warpData?.license_key}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t('pages.xray.warp.privateKey')}</td>
|
|
||||||
<td>{warpData?.private_key}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<Button loading={loading} type="primary" danger className="mt-8" icon={<DeleteOutlined />} onClick={delConfig}>
|
|
||||||
{t('pages.xray.warp.deleteAccount')}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<table className="warp-data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr className="row-odd">
|
||||||
|
<td>{t('pages.xray.warp.accessToken')}</td>
|
||||||
|
<td>{warpData?.access_token}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t('pages.xray.warp.deviceId')}</td>
|
||||||
|
<td>{warpData?.device_id}</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="row-odd">
|
||||||
|
<td>{t('pages.xray.warp.licenseKey')}</td>
|
||||||
|
<td>{warpData?.license_key}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t('pages.xray.warp.privateKey')}</td>
|
||||||
|
<td>{warpData?.private_key}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<Divider className="zero-margin">{t('pages.xray.warp.settings')}</Divider>
|
<Button loading={loading} type="primary" danger className="mt-8" icon={<DeleteOutlined />} onClick={delConfig}>
|
||||||
|
{t('pages.xray.warp.deleteAccount')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Collapse
|
<Divider className="zero-margin">{t('pages.xray.warp.settings')}</Divider>
|
||||||
className="my-10"
|
|
||||||
items={[
|
<Collapse
|
||||||
{
|
className="my-10"
|
||||||
key: '1',
|
items={[
|
||||||
label: t('pages.xray.warp.licenseKeyLabel'),
|
{
|
||||||
children: (
|
key: '1',
|
||||||
<Form colon={false} labelCol={{ md: { span: 6 } }} wrapperCol={{ md: { span: 14 } }}>
|
label: t('pages.xray.warp.licenseKeyLabel'),
|
||||||
<Form.Item label={t('pages.xray.warp.key')}>
|
children: (
|
||||||
<Input
|
<Form colon={false} labelCol={{ md: { span: 6 } }} wrapperCol={{ md: { span: 14 } }}>
|
||||||
value={warpPlus}
|
<Form.Item label={t('pages.xray.warp.key')}>
|
||||||
placeholder={t('pages.xray.warp.keyPlaceholder')}
|
<Input
|
||||||
onChange={(e) => {
|
value={warpPlus}
|
||||||
setWarpPlus(e.target.value);
|
placeholder={t('pages.xray.warp.keyPlaceholder')}
|
||||||
setLicenseError('');
|
onChange={(e) => {
|
||||||
}}
|
setWarpPlus(e.target.value);
|
||||||
/>
|
setLicenseError('');
|
||||||
<div className="license-actions mt-8">
|
}}
|
||||||
<Button
|
/>
|
||||||
type="primary"
|
<div className="license-actions mt-8">
|
||||||
disabled={warpPlus.length < 26}
|
<Button
|
||||||
loading={loading}
|
type="primary"
|
||||||
onClick={updateLicense}
|
disabled={warpPlus.length < 26}
|
||||||
>
|
loading={loading}
|
||||||
{t('update')}
|
onClick={updateLicense}
|
||||||
|
>
|
||||||
|
{t('update')}
|
||||||
|
</Button>
|
||||||
|
{licenseError && (
|
||||||
|
<Alert title={licenseError} type="error" showIcon className="license-error" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: t('pages.xray.warp.autoUpdateIp', 'Auto Update IP Address'),
|
||||||
|
children: (
|
||||||
|
<Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 12 } }}>
|
||||||
|
<Form.Item label={t('pages.xray.warp.intervalDays', 'Interval (Days)')}
|
||||||
|
tooltip={t('pages.xray.warp.intervalDesc', '0 to disable. Changes IP address automatically.')}>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
value={updateInterval}
|
||||||
|
onChange={(e) => setUpdateInterval(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
<Button className="mt-8" type="primary" loading={loading} onClick={saveInterval}>
|
||||||
|
{t('save', 'Save')}
|
||||||
</Button>
|
</Button>
|
||||||
{licenseError && (
|
</Form.Item>
|
||||||
<Alert title={licenseError} type="error" showIcon className="license-error" />
|
</Form>
|
||||||
)}
|
),
|
||||||
</div>
|
},
|
||||||
</Form.Item>
|
]}
|
||||||
</Form>
|
/>
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
label: t('pages.xray.warp.autoUpdateIp', 'Auto Update IP Address'),
|
|
||||||
children: (
|
|
||||||
<Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 12 } }}>
|
|
||||||
<Form.Item label={t('pages.xray.warp.intervalDays', 'Interval (Days)')} extra={t('pages.xray.warp.intervalDesc', '0 to disable. Changes IP address automatically.')}>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min={0}
|
|
||||||
value={updateInterval}
|
|
||||||
onChange={(e) => setUpdateInterval(Number(e.target.value))}
|
|
||||||
/>
|
|
||||||
<Button className="mt-8" type="primary" loading={loading} onClick={saveInterval}>
|
|
||||||
{t('save', 'Save')}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Divider className="zero-margin">{t('pages.xray.warp.accountInfo')}</Divider>
|
<Divider className="zero-margin">{t('pages.xray.warp.accountInfo')}</Divider>
|
||||||
<div className="my-8">
|
<div className="my-8">
|
||||||
<Button loading={loading} type="primary" icon={<SyncOutlined />} onClick={getConfig}>
|
<Button loading={loading} type="primary" icon={<SyncOutlined />} onClick={getConfig}>
|
||||||
{t('refresh')}
|
{t('refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button loading={loading} type="primary" className="ml-8" icon={<SyncOutlined />} onClick={changeIp}>
|
<Button loading={loading} type="primary" className="ml-8" icon={<SyncOutlined />} onClick={changeIp}>
|
||||||
{t('pages.xray.warp.changeIp', 'Change IP')}
|
{t('pages.xray.warp.changeIp', 'Change IP')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasConfig && (
|
{hasConfig && (
|
||||||
<>
|
<>
|
||||||
<table className="warp-data-table">
|
<table className="warp-data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr className="row-odd">
|
<tr className="row-odd">
|
||||||
<td>{t('pages.xray.warp.deviceName')}</td>
|
<td>{t('pages.xray.warp.deviceName')}</td>
|
||||||
<td>{warpConfig?.name}</td>
|
<td>{warpConfig?.name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t('pages.xray.warp.deviceModel')}</td>
|
<td>{t('pages.xray.warp.deviceModel')}</td>
|
||||||
<td>{warpConfig?.model}</td>
|
<td>{warpConfig?.model}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="row-odd">
|
<tr className="row-odd">
|
||||||
<td>{t('pages.xray.warp.deviceEnabled')}</td>
|
<td>{t('pages.xray.warp.deviceEnabled')}</td>
|
||||||
<td>{String(warpConfig?.enabled)}</td>
|
<td>{String(warpConfig?.enabled)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{warpConfig?.account && (
|
{warpConfig?.account && (
|
||||||
<>
|
<>
|
||||||
<tr>
|
|
||||||
<td>{t('pages.xray.warp.accountType')}</td>
|
|
||||||
<td>{warpConfig.account.account_type}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="row-odd">
|
|
||||||
<td>{t('pages.xray.warp.role')}</td>
|
|
||||||
<td>{warpConfig.account.role}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t('pages.xray.warp.warpPlusData')}</td>
|
|
||||||
<td>{SizeFormatter.sizeFormat(warpConfig.account.premium_data)}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="row-odd">
|
|
||||||
<td>{t('pages.xray.warp.quota')}</td>
|
|
||||||
<td>{SizeFormatter.sizeFormat(warpConfig.account.quota)}</td>
|
|
||||||
</tr>
|
|
||||||
{warpConfig.account.usage != null && (
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t('pages.xray.warp.usage')}</td>
|
<td>{t('pages.xray.warp.accountType')}</td>
|
||||||
<td>{SizeFormatter.sizeFormat(warpConfig.account.usage)}</td>
|
<td>{warpConfig.account.account_type}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
<tr className="row-odd">
|
||||||
</>
|
<td>{t('pages.xray.warp.role')}</td>
|
||||||
)}
|
<td>{warpConfig.account.role}</td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
<tr>
|
||||||
|
<td>{t('pages.xray.warp.warpPlusData')}</td>
|
||||||
|
<td>{SizeFormatter.sizeFormat(warpConfig.account.premium_data)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="row-odd">
|
||||||
|
<td>{t('pages.xray.warp.quota')}</td>
|
||||||
|
<td>{SizeFormatter.sizeFormat(warpConfig.account.quota)}</td>
|
||||||
|
</tr>
|
||||||
|
{warpConfig.account.usage != null && (
|
||||||
|
<tr>
|
||||||
|
<td>{t('pages.xray.warp.usage')}</td>
|
||||||
|
<td>{SizeFormatter.sizeFormat(warpConfig.account.usage)}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<Divider className="my-10">{t('pages.xray.outbound.outboundStatus')}</Divider>
|
<Divider className="my-10">{t('pages.xray.outbound.outboundStatus')}</Divider>
|
||||||
{warpOutboundIndex >= 0 ? (
|
{warpOutboundIndex >= 0 ? (
|
||||||
<>
|
<>
|
||||||
<Tag color="green">{t('enabled')}</Tag>
|
<Tag color="green">{t('enabled')}</Tag>
|
||||||
<Button type="primary" danger loading={loading} className="ml-8" onClick={resetOutbound}>
|
<Button type="primary" danger loading={loading} className="ml-8" onClick={resetOutbound}>
|
||||||
{t('reset')}
|
{t('reset')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Tag color="orange">{t('disabled')}</Tag>
|
<Tag color="orange">{t('disabled')}</Tag>
|
||||||
<Button type="primary" loading={loading} className="ml-8" icon={<PlusOutlined />} onClick={addOutbound}>
|
<Button type="primary" loading={loading} className="ml-8" icon={<PlusOutlined />} onClick={addOutbound}>
|
||||||
{t('pages.xray.warp.addOutbound')}
|
{t('pages.xray.warp.addOutbound')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ func copyTable(src, dst *gorm.DB, mdl any) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows := make([]map[string]any, n)
|
rows := make([]map[string]any, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := range n {
|
||||||
rv := reflect.Indirect(slice.Index(i))
|
rv := reflect.Indirect(slice.Index(i))
|
||||||
row := make(map[string]any, len(columns))
|
row := make(map[string]any, len(columns))
|
||||||
for _, name := range columns {
|
for _, name := range columns {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func TestGetLogs_ReturnsAtMostC(t *testing.T) {
|
|||||||
logBufferMu.Lock()
|
logBufferMu.Lock()
|
||||||
logBuffer = nil
|
logBuffer = nil
|
||||||
logBufferMu.Unlock()
|
logBufferMu.Unlock()
|
||||||
for i := 0; i < 5; i++ {
|
for i := range 5 {
|
||||||
addToBuffer("ERROR", fmt.Sprintf("m%d", i))
|
addToBuffer("ERROR", fmt.Sprintf("m%d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -425,12 +425,12 @@ func parseMetricLine(line string) (name string, labels map[string]string, value
|
|||||||
if end < brace {
|
if end < brace {
|
||||||
return "", nil, 0, fmt.Errorf("malformed metric line")
|
return "", nil, 0, fmt.Errorf("malformed metric line")
|
||||||
}
|
}
|
||||||
for _, kv := range strings.Split(line[brace+1:end], ",") {
|
for kv := range strings.SplitSeq(line[brace+1:end], ",") {
|
||||||
eq := strings.IndexByte(kv, '=')
|
before, after, ok := strings.Cut(kv, "=")
|
||||||
if eq < 0 {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
labels[strings.TrimSpace(kv[:eq])] = strings.Trim(strings.TrimSpace(kv[eq+1:]), `"`)
|
labels[strings.TrimSpace(before)] = strings.Trim(strings.TrimSpace(after), `"`)
|
||||||
}
|
}
|
||||||
rest = strings.TrimSpace(line[end+1:])
|
rest = strings.TrimSpace(line[end+1:])
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -220,8 +220,8 @@ func clashStringList(v any) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stripCIDR(addr string) string {
|
func stripCIDR(addr string) string {
|
||||||
if i := strings.IndexByte(addr, '/'); i >= 0 {
|
if before, _, ok := strings.Cut(addr, "/"); ok {
|
||||||
return addr[:i]
|
return before
|
||||||
}
|
}
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,9 +147,9 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
writeSubError(c, err)
|
writeSubError(c, err)
|
||||||
} else {
|
} else {
|
||||||
result := ""
|
var result strings.Builder
|
||||||
for _, sub := range subs {
|
for _, sub := range subs {
|
||||||
result += sub + "\n"
|
result.WriteString(sub + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
|
// If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
|
||||||
@@ -180,9 +180,9 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
|
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
|
||||||
|
|
||||||
if a.subEncrypt {
|
if a.subEncrypt {
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result.String())))
|
||||||
} else {
|
} else {
|
||||||
c.String(200, result)
|
c.String(200, result.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,11 +397,11 @@ func parseShadowsocks(link string) (*ParseResult, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func splitMethodPass(userInfo string) (string, string) {
|
func splitMethodPass(userInfo string) (string, string) {
|
||||||
colon := strings.Index(userInfo, ":")
|
before, after, ok := strings.Cut(userInfo, ":")
|
||||||
if colon < 0 {
|
if !ok {
|
||||||
return "2022-blake3-aes-128-gcm", userInfo // guess
|
return "2022-blake3-aes-128-gcm", userInfo // guess
|
||||||
}
|
}
|
||||||
return userInfo[:colon], userInfo[colon+1:]
|
return before, after
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- hysteria2 ---
|
// --- hysteria2 ---
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ func (a *XraySettingController) testOutbounds(c *gin.Context) {
|
|||||||
func (a *XraySettingController) balancerStatus(c *gin.Context) {
|
func (a *XraySettingController) balancerStatus(c *gin.Context) {
|
||||||
raw := c.PostForm("tags")
|
raw := c.PostForm("tags")
|
||||||
var tags []string
|
var tags []string
|
||||||
for _, tag := range strings.Split(raw, ",") {
|
for tag := range strings.SplitSeq(raw, ",") {
|
||||||
if tag = strings.TrimSpace(tag); tag != "" {
|
if tag = strings.TrimSpace(tag); tag != "" {
|
||||||
tags = append(tags, tag)
|
tags = append(tags, tag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ type ValidationPayload struct {
|
|||||||
func writeBindFailure(c *gin.Context, err error) {
|
func writeBindFailure(c *gin.Context, err error) {
|
||||||
payload := ValidationPayload{Issues: []FieldIssue{}, Message: err.Error()}
|
payload := ValidationPayload{Issues: []FieldIssue{}, Message: err.Error()}
|
||||||
|
|
||||||
var ve validator.ValidationErrors
|
if ve, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||||
if errors.As(err, &ve) {
|
|
||||||
payload.Issues = make([]FieldIssue, 0, len(ve))
|
payload.Issues = make([]FieldIssue, 0, len(ve))
|
||||||
for _, fe := range ve {
|
for _, fe := range ve {
|
||||||
payload.Issues = append(payload.Issues, FieldIssue{
|
payload.Issues = append(payload.Issues, FieldIssue{
|
||||||
@@ -102,7 +101,7 @@ func writeBindFailure(c *gin.Context, err error) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
||||||
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
name, _, _ := strings.Cut(fld.Tag.Get("json"), ",")
|
||||||
if name == "-" || name == "" {
|
if name == "-" || name == "" {
|
||||||
return fld.Name
|
return fld.Name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func TestAllAPIsPostgresScale(t *testing.T) {
|
|||||||
db.Exec("ANALYZE")
|
db.Exec("ANALYZE")
|
||||||
|
|
||||||
emails := make([]string, n)
|
emails := make([]string, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := range n {
|
||||||
emails[i] = clients[i].Email
|
emails[i] = clients[i].Email
|
||||||
}
|
}
|
||||||
emailsM := emails[:m]
|
emailsM := emails[:m]
|
||||||
@@ -196,7 +196,7 @@ func TestGetClientTrafficByEmailABScale(t *testing.T) {
|
|||||||
targets := []string{clients[0].Email, clients[n/2].Email, clients[n-1].Email}
|
targets := []string{clients[0].Email, clients[n/2].Email, clients[n-1].Email}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for i := 0; i < reps; i++ {
|
for i := range reps {
|
||||||
if _, err := inboundSvc.GetClientTrafficByEmail(targets[i%len(targets)]); err != nil {
|
if _, err := inboundSvc.GetClientTrafficByEmail(targets[i%len(targets)]); err != nil {
|
||||||
t.Fatalf("new GetClientTrafficByEmail: %v", err)
|
t.Fatalf("new GetClientTrafficByEmail: %v", err)
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ func TestGetClientTrafficByEmailABScale(t *testing.T) {
|
|||||||
newDur := time.Since(start) / reps
|
newDur := time.Since(start) / reps
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
for i := 0; i < reps; i++ {
|
for i := range reps {
|
||||||
if err := oldImpl(targets[i%len(targets)]); err != nil {
|
if err := oldImpl(targets[i%len(targets)]); err != nil {
|
||||||
t.Fatalf("old GetClientTrafficByEmail: %v", err)
|
t.Fatalf("old GetClientTrafficByEmail: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ func parseRecipients(toStr string) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var out []string
|
var out []string
|
||||||
for _, s := range strings.Split(toStr, ",") {
|
for s := range strings.SplitSeq(toStr, ",") {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
if s != "" {
|
if s != "" {
|
||||||
out = append(out, s)
|
out = append(out, s)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (s *Subscriber) isEventEnabled(t eventbus.EventType) bool {
|
|||||||
if err != nil || events == "" {
|
if err != nil || events == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, e := range strings.Split(events, ",") {
|
for e := range strings.SplitSeq(events, ",") {
|
||||||
if strings.TrimSpace(e) == string(t) {
|
if strings.TrimSpace(e) == string(t) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,8 +190,8 @@ func (s *WarpService) ChangeWarpIP() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var parsed struct {
|
var parsed struct {
|
||||||
Data map[string]string `json:"data"`
|
Data map[string]string `json:"data"`
|
||||||
Config map[string]interface{} `json:"config"`
|
Config map[string]any `json:"config"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
|
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -106,8 +107,8 @@ func (h *metricHistory) aggregate(metric string, bucketSeconds int, maxPoints in
|
|||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
hist := h.metrics[metric]
|
hist := h.metrics[metric]
|
||||||
startIdx := 0
|
startIdx := 0
|
||||||
for i := len(hist) - 1; i >= 0; i-- {
|
for i, h := range slices.Backward(hist) {
|
||||||
if hist[i].T < cutoff {
|
if h.T < cutoff {
|
||||||
startIdx = i + 1
|
startIdx = i + 1
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -405,10 +406,8 @@ func (s *NodeService) EnsureInboundTagAllowed(nodeID int, tag string) error {
|
|||||||
if node.InboundSyncMode != "selected" {
|
if node.InboundSyncMode != "selected" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, t := range node.InboundTags {
|
if slices.Contains(node.InboundTags, tag) {
|
||||||
if t == tag {
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
buf, err := json.Marshal(append(node.InboundTags, tag))
|
buf, err := json.Marshal(append(node.InboundTags, tag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func syncInboundOld(tx *gorm.DB, inboundId int, clients []model.Client) error {
|
|||||||
|
|
||||||
func makeScaleClients(n int) []model.Client {
|
func makeScaleClients(n int) []model.Client {
|
||||||
out := make([]model.Client, n)
|
out := make([]model.Client, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := range n {
|
||||||
out[i] = model.Client{
|
out[i] = model.Client{
|
||||||
ID: uuid.NewString(),
|
ID: uuid.NewString(),
|
||||||
Email: fmt.Sprintf("user-%07d@scale", i),
|
Email: fmt.Sprintf("user-%07d@scale", i),
|
||||||
@@ -260,7 +260,7 @@ func TestGroupAndListPostgresScale(t *testing.T) {
|
|||||||
}
|
}
|
||||||
db.Exec("ANALYZE")
|
db.Exec("ANALYZE")
|
||||||
emails := make([]string, n)
|
emails := make([]string, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := range n {
|
||||||
emails[i] = clients[i].Email
|
emails[i] = clients[i].Email
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ func TestBulkOpsPostgresScale(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emailsM := make([]string, m)
|
emailsM := make([]string, m)
|
||||||
for i := 0; i < m; i++ {
|
for i := range m {
|
||||||
emailsM[i] = clients[i].Email
|
emailsM[i] = clients[i].Email
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +405,7 @@ func TestBulkOpsPostgresScale(t *testing.T) {
|
|||||||
detachDur := time.Since(t0)
|
detachDur := time.Since(t0)
|
||||||
|
|
||||||
payloads := make([]ClientCreatePayload, m)
|
payloads := make([]ClientCreatePayload, m)
|
||||||
for i := 0; i < m; i++ {
|
for i := range m {
|
||||||
payloads[i] = ClientCreatePayload{
|
payloads[i] = ClientCreatePayload{
|
||||||
Client: model.Client{ID: uuid.NewString(), Email: fmt.Sprintf("bulknew-%07d@scale", i), SubID: fmt.Sprintf("bnsub-%07d", i), Enable: true},
|
Client: model.Client{ID: uuid.NewString(), Email: fmt.Sprintf("bulknew-%07d@scale", i), SubID: fmt.Sprintf("bnsub-%07d", i), Enable: true},
|
||||||
InboundIds: []int{ib.Id},
|
InboundIds: []int{ib.Id},
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (t *Tgbot) isEventEnabled(eventType eventbus.EventType) bool {
|
|||||||
if err != nil || events == "" {
|
if err != nil || events == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, e := range strings.Split(events, ",") {
|
for e := range strings.SplitSeq(events, ",") {
|
||||||
if strings.TrimSpace(e) == string(eventType) {
|
if strings.TrimSpace(e) == string(eventType) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,39 +41,39 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XraySettingService) UpdateWarpXraySetting(warpData map[string]string, warpConfig map[string]interface{}) error {
|
func (s *XraySettingService) UpdateWarpXraySetting(warpData map[string]string, warpConfig map[string]any) error {
|
||||||
template, err := s.GetXrayConfigTemplate()
|
template, err := s.GetXrayConfigTemplate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg map[string]interface{}
|
var cfg map[string]any
|
||||||
if err := json.Unmarshal([]byte(template), &cfg); err != nil {
|
if err := json.Unmarshal([]byte(template), &cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
outbounds, ok := cfg["outbounds"].([]interface{})
|
outbounds, ok := cfg["outbounds"].([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
updated := false
|
updated := false
|
||||||
for _, outIface := range outbounds {
|
for _, outIface := range outbounds {
|
||||||
out, ok := outIface.(map[string]interface{})
|
out, ok := outIface.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if tag, ok := out["tag"].(string); ok && tag == "warp" {
|
if tag, ok := out["tag"].(string); ok && tag == "warp" {
|
||||||
settings, ok := out["settings"].(map[string]interface{})
|
settings, ok := out["settings"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
settings["secretKey"] = warpData["private_key"]
|
settings["secretKey"] = warpData["private_key"]
|
||||||
|
|
||||||
if conf, ok := warpConfig["config"].(map[string]interface{}); ok {
|
if conf, ok := warpConfig["config"].(map[string]any); ok {
|
||||||
if iface, ok := conf["interface"].(map[string]interface{}); ok {
|
if iface, ok := conf["interface"].(map[string]any); ok {
|
||||||
if addrs, ok := iface["addresses"].(map[string]interface{}); ok {
|
if addrs, ok := iface["addresses"].(map[string]any); ok {
|
||||||
var addrList []string
|
var addrList []string
|
||||||
if v4, ok := addrs["v4"].(string); ok && v4 != "" {
|
if v4, ok := addrs["v4"].(string); ok && v4 != "" {
|
||||||
addrList = append(addrList, v4+"/32")
|
addrList = append(addrList, v4+"/32")
|
||||||
@@ -100,12 +100,12 @@ func (s *XraySettingService) UpdateWarpXraySetting(warpData map[string]string, w
|
|||||||
settings["reserved"] = res
|
settings["reserved"] = res
|
||||||
}
|
}
|
||||||
|
|
||||||
if peers, ok := conf["peers"].([]interface{}); ok && len(peers) > 0 {
|
if peers, ok := conf["peers"].([]any); ok && len(peers) > 0 {
|
||||||
if peer, ok := peers[0].(map[string]interface{}); ok {
|
if peer, ok := peers[0].(map[string]any); ok {
|
||||||
if pSettings, ok := settings["peers"].([]interface{}); ok && len(pSettings) > 0 {
|
if pSettings, ok := settings["peers"].([]any); ok && len(pSettings) > 0 {
|
||||||
if pSet, ok := pSettings[0].(map[string]interface{}); ok {
|
if pSet, ok := pSettings[0].(map[string]any); ok {
|
||||||
pSet["publicKey"] = peer["public_key"]
|
pSet["publicKey"] = peer["public_key"]
|
||||||
if endpoint, ok := peer["endpoint"].(map[string]interface{}); ok {
|
if endpoint, ok := peer["endpoint"].(map[string]any); ok {
|
||||||
pSet["endpoint"] = endpoint["host"]
|
pSet["endpoint"] = endpoint["host"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -388,7 +388,7 @@ func (s *Server) cpuAlarmWanted() bool {
|
|||||||
if threshold <= 0 {
|
if threshold <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, e := range strings.Split(events, ",") {
|
for e := range strings.SplitSeq(events, ",") {
|
||||||
if strings.TrimSpace(e) == string(eventbus.EventCPUHigh) {
|
if strings.TrimSpace(e) == string(eventbus.EventCPUHigh) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -248,12 +249,7 @@ func TestRefreshLocalOnline_GraceBoundaryInbounds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func containsString(s []string, v string) bool {
|
func containsString(s []string, v string) bool {
|
||||||
for _, x := range s {
|
return slices.Contains(s, v)
|
||||||
if x == v {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -116,9 +117,7 @@ func isVisitedRef(t TypeRef, visited map[string]bool) bool {
|
|||||||
|
|
||||||
func cloneVisited(in map[string]bool) map[string]bool {
|
func cloneVisited(in map[string]bool) map[string]bool {
|
||||||
out := make(map[string]bool, len(in)+1)
|
out := make(map[string]bool, len(in)+1)
|
||||||
for k, v := range in {
|
maps.Copy(out, in)
|
||||||
out[k] = v
|
|
||||||
}
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user